[MERGE] mass_mailing: clean module organization and prepare SMS addition
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. Move some code bits / fields declarations to reorder them. Purpose is to make code easier to understand and find. Funny we found two override of create that have been merged. An override of name create is not necessary considering code was present in one of the two merged create. Make internal methods private and rename send_mail to action_send_mail to avoid confusion with composer and template send_mail methods. Fix UTM management and propagation in mass mailing. Right UTM definition in mass mailing : * mass mailing campaign -> utm.campaign * mass mailing -> utm.source * "email" -> utm.medium Therefore : * remove campaign setting source and medium as each mailing is a source and medium is "email"; * ensure each mailing is a separate source (otherwise name is shared); * ensure UTM values propagate to link creation are those values and not the one coming from the mass mailing campaign; Rename mass mailing models. Rationale : * remove mail prefix, as mass mailing will allow to use SMS to contact people and will soon be less mail-dependent; * have simpler name, easier to read, find and understand; * mailing.list/contact/tag seems a better naming than mail.mass_mailing. list/contact/tag; * have a mailing prefix for all models used in mass mailing; MIGRATION mail.mail.statistics model -> mailing.trace mail.statistics.report model -> mail.trace.report mail.mass_mailing.list model -> mailing.list mail.mass_mailing.list.merge model -> mailing.list.merge mail.mass_mailing.contact model -> mailing.contact mail.mass_mailing.list_contact_rel model -> mailing.contact.subscription mail_mass_mailing_contact_list_rel table -> mailing_contact_list_rel (specific case of a decorated m2m) mail.mass_mailing.stage model -> mailing.stage mail.mass_mailing.tag model -> mailing.tag mail.mass_mailing model -> mailing.mailing fields updated (w column change) * link.tracker.click: mail_stat_id -> mailing_trace_id fields updated (no column change) * mail.mail: statistics_ids -> mailing_trace_ids * mail.mass_mailing (mailing.mailing): statistics_ids -> mailing_trace_ids * mail.mass_mailing.list (mailing.list): subscription_contact_ids -> subscription_ids LINKS Task ID 2037906 Preparing task ID 1997464 (SMS addition in mass mailing) PR #34938 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -3,4 +3,5 @@
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
from . import report
|
||||
from . import wizard
|
||||
|
||||
@@ -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',
|
||||
'wizard/mailing_list_merge_views.xml',
|
||||
'wizard/mailing_mailing_schedule_date_views.xml',
|
||||
'wizard/mailing_mailing_test_views.xml',
|
||||
'views/mailing_mailing_views_menus.xml',
|
||||
'views/mailing_trace_views.xml',
|
||||
'views/link_tracker_views.xml',
|
||||
'views/mass_mailing_template.xml',
|
||||
'views/mass_mailing_views.xml',
|
||||
'views/mailing_contact_views.xml',
|
||||
'views/mailing_list_views.xml',
|
||||
'views/mailing_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/mailing_trace_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',
|
||||
|
||||
@@ -14,11 +14,11 @@ class MassMailController(http.Controller):
|
||||
def _valid_unsubscribe_token(self, mailing_id, res_id, email, token):
|
||||
if not (mailing_id and res_id and email and token):
|
||||
return False
|
||||
mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
|
||||
mailing = request.env['mailing.mailing'].sudo().browse(mailing_id)
|
||||
return consteq(mailing._unsubscribe_token(res_id, email), token)
|
||||
|
||||
def _log_blacklist_action(self, blacklist_entry, mailing_id, description):
|
||||
mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
|
||||
mailing = request.env['mailing.mailing'].sudo().browse(mailing_id)
|
||||
model_display = mailing.mailing_model_id.display_name
|
||||
blacklist_entry._message_log(body=description + " ({})".format(model_display))
|
||||
|
||||
@@ -29,17 +29,17 @@ class MassMailController(http.Controller):
|
||||
|
||||
@http.route(['/mail/mailing/<int:mailing_id>/unsubscribe'], type='http', website=True, auth='public')
|
||||
def mailing(self, mailing_id, email=None, res_id=None, token="", **post):
|
||||
mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
|
||||
mailing = request.env['mailing.mailing'].sudo().browse(mailing_id)
|
||||
if mailing.exists():
|
||||
res_id = res_id and int(res_id)
|
||||
if not self._valid_unsubscribe_token(mailing_id, res_id, email, str(token)):
|
||||
raise exceptions.AccessDenied()
|
||||
|
||||
if mailing.mailing_model_real == 'mail.mass_mailing.contact':
|
||||
if mailing.mailing_model_real == 'mailing.contact':
|
||||
# Unsubscribe directly + Let the user choose his subscriptions
|
||||
mailing.update_opt_out(email, mailing.contact_list_ids.ids, True)
|
||||
|
||||
contacts = request.env['mail.mass_mailing.contact'].sudo().search([('email_normalized', '=', tools.email_normalize(email))])
|
||||
contacts = request.env['mailing.contact'].sudo().search([('email_normalized', '=', tools.email_normalize(email))])
|
||||
subscription_list_ids = contacts.mapped('subscription_list_ids')
|
||||
# In many user are found : if user is opt_out on the list with contact_id 1 but not with contact_id 2,
|
||||
# assume that the user is not opt_out on both
|
||||
@@ -49,7 +49,7 @@ class MassMailController(http.Controller):
|
||||
opt_out_list_ids = set([list.id for list in opt_out_list_ids if list not in opt_in_list_ids])
|
||||
|
||||
unique_list_ids = set([list.list_id.id for list in subscription_list_ids])
|
||||
list_ids = request.env['mail.mass_mailing.list'].sudo().browse(unique_list_ids)
|
||||
list_ids = request.env['mailing.list'].sudo().browse(unique_list_ids)
|
||||
unsubscribed_list = ', '.join(str(list.name) for list in mailing.contact_list_ids if list.is_public)
|
||||
return request.render('mass_mailing.page_unsubscribe', {
|
||||
'contacts': contacts,
|
||||
@@ -62,7 +62,7 @@ class MassMailController(http.Controller):
|
||||
'show_blacklist_button': request.env['ir.config_parameter'].sudo().get_param('mass_mailing.show_blacklist_buttons'),
|
||||
})
|
||||
else:
|
||||
opt_in_lists = request.env['mail.mass_mailing.list_contact_rel'].sudo().search([
|
||||
opt_in_lists = request.env['mailing.contact.subscription'].sudo().search([
|
||||
('contact_id.email_normalized', '=', email),
|
||||
('opt_out', '=', False)
|
||||
]).mapped('list_id')
|
||||
@@ -82,7 +82,7 @@ class MassMailController(http.Controller):
|
||||
|
||||
@http.route('/mail/mailing/unsubscribe', type='json', auth='public')
|
||||
def unsubscribe(self, mailing_id, opt_in_ids, opt_out_ids, email, res_id, token):
|
||||
mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
|
||||
mailing = request.env['mailing.mailing'].sudo().browse(mailing_id)
|
||||
if mailing.exists():
|
||||
if not self._valid_unsubscribe_token(mailing_id, res_id, email, token):
|
||||
return 'unauthorized'
|
||||
@@ -94,15 +94,15 @@ class MassMailController(http.Controller):
|
||||
@http.route('/mail/track/<int:mail_id>/blank.gif', type='http', auth='public')
|
||||
def track_mail_open(self, mail_id, **post):
|
||||
""" Email tracking. """
|
||||
request.env['mail.mail.statistics'].sudo().set_opened(mail_mail_ids=[mail_id])
|
||||
request.env['mailing.trace'].sudo().set_opened(mail_mail_ids=[mail_id])
|
||||
response = werkzeug.wrappers.Response()
|
||||
response.mimetype = 'image/gif'
|
||||
response.data = base64.b64decode(b'R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==')
|
||||
|
||||
return response
|
||||
|
||||
@http.route('/r/<string:code>/m/<int:stat_id>', type='http', auth="public")
|
||||
def full_url_redirect(self, code, stat_id, **post):
|
||||
@http.route('/r/<string:code>/m/<int:mailing_trace_id>', type='http', auth="public")
|
||||
def full_url_redirect(self, code, mailing_trace_id, **post):
|
||||
# don't assume geoip is set, it is part of the website module
|
||||
# which mass_mailing doesn't depend on
|
||||
country_code = request.session.get('geoip', False) and request.session.geoip.get('country_code', False)
|
||||
@@ -111,7 +111,7 @@ class MassMailController(http.Controller):
|
||||
code,
|
||||
ip=request.httprequest.remote_addr,
|
||||
country_code=country_code,
|
||||
mail_stat_id=stat_id
|
||||
mailing_trace_id=mailing_trace_id
|
||||
)
|
||||
return werkzeug.utils.redirect(request.env['link.tracker'].get_url_from_code(code), 301)
|
||||
|
||||
@@ -152,7 +152,7 @@ class MassMailController(http.Controller):
|
||||
|
||||
@http.route('/mailing/feedback', type='json', auth='public')
|
||||
def send_feedback(self, mailing_id, res_id, email, feedback, token):
|
||||
mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
|
||||
mailing = request.env['mailing.mailing'].sudo().browse(mailing_id)
|
||||
if mailing.exists() and email:
|
||||
if not self._valid_unsubscribe_token(mailing_id, res_id, email, token):
|
||||
return 'unauthorized'
|
||||
|
||||
+2
-19
@@ -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">
|
||||
<!DOCTYPE html>
|
||||
@@ -34,4 +17,4 @@
|
||||
</body>
|
||||
</html>
|
||||
</template>
|
||||
</odoo>
|
||||
</data></odoo>
|
||||
@@ -3,15 +3,15 @@
|
||||
<data noupdate="1">
|
||||
|
||||
<!-- Default stages of mass mailing campaigns -->
|
||||
<record id="campaign_stage_1" model="mail.mass_mailing.stage">
|
||||
<record id="campaign_stage_1" model="mailing.stage">
|
||||
<field name="name">Schedule</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="campaign_stage_2" model="mail.mass_mailing.stage">
|
||||
<record id="campaign_stage_2" model="mailing.stage">
|
||||
<field name="name">Design</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="campaign_stage_3" model="mail.mass_mailing.stage">
|
||||
<record id="campaign_stage_3" model="mailing.stage">
|
||||
<field name="name">Sent</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
@@ -19,7 +19,7 @@
|
||||
<!-- Cron that process the mass mailing queue -->
|
||||
<record id="ir_cron_mass_mailing_queue" model="ir.cron">
|
||||
<field name="name">Mass Mailing: Process queue</field>
|
||||
<field name="model_id" ref="model_mail_mass_mailing"/>
|
||||
<field name="model_id" ref="model_mailing_mailing"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._process_mass_mailing_queue()</field>
|
||||
<field name="user_id" ref="base.user_root" />
|
||||
|
||||
@@ -8,38 +8,38 @@
|
||||
</record>
|
||||
|
||||
<!-- Create mailing lists -->
|
||||
<record id="mass_mail_list_2" model="mail.mass_mailing.list">
|
||||
<record id="mass_mail_list_2" model="mailing.list">
|
||||
<field name="name">Imported Contacts</field>
|
||||
</record>
|
||||
|
||||
<!-- Create Contacts -->
|
||||
<record id="mass_mail_contact_1" model="mail.mass_mailing.contact">
|
||||
<record id="mass_mail_contact_1" model="mailing.contact">
|
||||
<field name="name">Aristide Antario</field>
|
||||
<field name="email">aa@example.com</field>
|
||||
<field name="list_ids" eval="[(6,0,[ref('mass_mailing.mass_mail_list_2')])]"/>
|
||||
</record>
|
||||
<record id="mass_mail_contact_2" model="mail.mass_mailing.contact">
|
||||
<record id="mass_mail_contact_2" model="mailing.contact">
|
||||
<field name="name">Beverly Bridge</field>
|
||||
<field name="email">bb@example.com</field>
|
||||
<field name="list_ids" eval="[(6,0,[ref('mass_mailing.mass_mail_list_2')])]"/>
|
||||
</record>
|
||||
<record id="mass_mail_contact_3" model="mail.mass_mailing.contact">
|
||||
<record id="mass_mail_contact_3" model="mailing.contact">
|
||||
<field name="name">Carol Cartridge</field>
|
||||
<field name="email">cc@example.com</field>
|
||||
<field name="list_ids" eval="[(6,0,[ref('mass_mailing.mass_mail_list_2')])]"/>
|
||||
</record>
|
||||
<record id="mass_mail_contact_4" model="mail.mass_mailing.contact">
|
||||
<record id="mass_mail_contact_4" model="mailing.contact">
|
||||
<field name="name">David Dawson</field>
|
||||
<field name="email">dd@example.com</field>
|
||||
</record>
|
||||
<record id="mass_mail_contact_5" model="mail.mass_mailing.contact">
|
||||
<record id="mass_mail_contact_5" model="mailing.contact">
|
||||
<field name="name">Elsa Ericson</field>
|
||||
<field name="email">ee@example.com</field>
|
||||
<field name="list_ids" eval="[(6,0,[ref('mass_mailing.mass_mail_list_2')])]"/>
|
||||
</record>
|
||||
|
||||
<!-- Create Opt-out Records -->
|
||||
<record id="mass_mail_contact_list_rel_1" model="mail.mass_mailing.list_contact_rel">
|
||||
<record id="mass_mail_contact_list_rel_1" model="mailing.contact.subscription">
|
||||
<field name="list_id" eval="ref('mass_mailing.mass_mail_list_2')"/>
|
||||
<field name="contact_id" eval="ref('mass_mailing.mass_mail_contact_4')"/>
|
||||
<field name="opt_out">True</field>
|
||||
@@ -51,7 +51,7 @@
|
||||
</record>
|
||||
|
||||
<!-- Create campaign and mailings -->
|
||||
<record id="mass_mail_tag_1" model="mail.mass_mailing.tag">
|
||||
<record id="mass_mail_tag_1" model="mailing.tag">
|
||||
<field name="name">Marketing</field>
|
||||
<field name="color" eval="1"/>
|
||||
</record>
|
||||
@@ -68,7 +68,7 @@
|
||||
<field name="tag_ids" eval="[(6,0,[ref('mass_mailing.mass_mail_tag_1')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="mass_mail_1" model="mail.mass_mailing">
|
||||
<record id="mass_mail_1" model="mailing.mailing">
|
||||
<field name="name">Newsletter 1</field>
|
||||
<field name="subject" eval="'%s Newsletter' % (DateTime.today().strftime('%B'))"/>
|
||||
<field name="state">done</field>
|
||||
@@ -78,7 +78,6 @@
|
||||
<field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="source_id" ref="mass_mailing.utm_source_0"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
<field name="mailing_model_id" ref="base.model_res_partner"/>
|
||||
<field name="mailing_domain" eval="[('parent_id', '=', ref('base.res_partner_4'))]"/>
|
||||
<field name="reply_to_mode">email</field>
|
||||
@@ -197,9 +196,9 @@
|
||||
<field name="attachment_ids" eval="[(4, ref('mass_mail_attach_1'))]"/>
|
||||
</record>
|
||||
<!-- Generate link tracker information from it -->
|
||||
<function model="mail.mass_mailing" name="convert_links" eval="[ref('mass_mailing.mass_mail_1')]"/>
|
||||
<function model="mailing.mailing" name="convert_links" eval="[ref('mass_mailing.mass_mail_1')]"/>
|
||||
|
||||
<record id="mass_mail_1_stat_0" model="mail.mail.statistics">
|
||||
<record id="mass_mail_1_stat_0" model="mailing.trace">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||
<field name="message_id">1111000@odoo.com</field>
|
||||
<field name="model">res.partner</field>
|
||||
@@ -208,7 +207,7 @@
|
||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="replied" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
</record>
|
||||
<record id="mass_mail_1_stat_1" model="mail.mail.statistics">
|
||||
<record id="mass_mail_1_stat_1" model="mailing.trace">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||
<field name="message_id">1111001@odoo.com</field>
|
||||
<field name="model">res.partner</field>
|
||||
@@ -217,7 +216,7 @@
|
||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="replied" eval="(DateTime.today() - relativedelta(days=0)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
</record>
|
||||
<record id="mass_mail_1_stat_2" model="mail.mail.statistics">
|
||||
<record id="mass_mail_1_stat_2" model="mailing.trace">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||
<field name="message_id">1111002@odoo.com</field>
|
||||
<field name="model">res.partner</field>
|
||||
@@ -225,7 +224,7 @@
|
||||
<field name="sent" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
</record>
|
||||
<record id="mass_mail_1_stat_3" model="mail.mail.statistics">
|
||||
<record id="mass_mail_1_stat_3" model="mailing.trace">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||
<field name="message_id">1111003@odoo.com</field>
|
||||
<field name="model">res.partner</field>
|
||||
@@ -233,7 +232,7 @@
|
||||
<field name="sent" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
</record>
|
||||
<record id="mass_mail_1_stat_4" model="mail.mail.statistics">
|
||||
<record id="mass_mail_1_stat_4" model="mailing.trace">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||
<field name="message_id">1111004@odoo.com</field>
|
||||
<field name="model">res.partner</field>
|
||||
@@ -241,7 +240,7 @@
|
||||
<field name="sent" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
</record>
|
||||
<record id="mass_mail_1_stat_5" model="mail.mail.statistics">
|
||||
<record id="mass_mail_1_stat_5" model="mailing.trace">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||
<field name="message_id">1111004@odoo.com</field>
|
||||
<field name="model">res.partner</field>
|
||||
@@ -249,7 +248,7 @@
|
||||
<field name="sent" eval="False"/>
|
||||
<field name="exception" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
</record>
|
||||
<record id="mass_mail_1_stat_6" model="mail.mail.statistics">
|
||||
<record id="mass_mail_1_stat_6" model="mailing.trace">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||
<field name="message_id">1111004@odoo.com</field>
|
||||
<field name="model">res.partner</field>
|
||||
@@ -265,7 +264,7 @@
|
||||
use="code"/>
|
||||
<value name="ip">100.01.02.03</value>
|
||||
<value name="country_code">BE</value>
|
||||
<value name="mail_stat_id" eval="ref('mass_mail_1_stat_0')"/>
|
||||
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_0')"/>
|
||||
</function>
|
||||
<function model="link.tracker.click" name="add_click">
|
||||
<value model="link.tracker.code"
|
||||
@@ -273,7 +272,7 @@
|
||||
use="code"/>
|
||||
<value name="ip">100.01.02.03</value>
|
||||
<value name="country_code">BE</value>
|
||||
<value name="mail_stat_id" eval="ref('mass_mail_1_stat_0')"/>
|
||||
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_0')"/>
|
||||
</function>
|
||||
<function model="link.tracker.click" name="add_click">
|
||||
<value model="link.tracker.code"
|
||||
@@ -281,7 +280,7 @@
|
||||
use="code"/>
|
||||
<value name="ip">100.01.02.04</value>
|
||||
<value name="country_code">BE</value>
|
||||
<value name="mail_stat_id" eval="ref('mass_mail_1_stat_1')"/>
|
||||
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_1')"/>
|
||||
</function>
|
||||
<function model="link.tracker.click" name="add_click">
|
||||
<value model="link.tracker.code"
|
||||
@@ -289,7 +288,7 @@
|
||||
use="code"/>
|
||||
<value name="ip">100.01.02.04</value>
|
||||
<value name="country_code">BE</value>
|
||||
<value name="mail_stat_id" eval="ref('mass_mail_1_stat_0')"/>
|
||||
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_0')"/>
|
||||
</function>
|
||||
<function model="link.tracker.click" name="add_click">
|
||||
<value model="link.tracker.code"
|
||||
@@ -297,7 +296,7 @@
|
||||
use="code"/>
|
||||
<value name="ip">100.01.02.05</value>
|
||||
<value name="country_code">BE</value>
|
||||
<value name="mail_stat_id" eval="ref('mass_mail_1_stat_2')"/>
|
||||
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_2')"/>
|
||||
</function>
|
||||
|
||||
</data>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import mass_mailing
|
||||
from . import mass_mailing_stats
|
||||
from . import link_tracker
|
||||
from . import mailing_contact
|
||||
from . import mailing_list
|
||||
from . import mailing_trace
|
||||
from . import mailing
|
||||
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
|
||||
|
||||
@@ -7,29 +7,29 @@ from odoo import api, fields, models
|
||||
class LinkTracker(models.Model):
|
||||
_inherit = "link.tracker"
|
||||
|
||||
mass_mailing_id = fields.Many2one('mail.mass_mailing', string='Mass Mailing')
|
||||
mass_mailing_id = fields.Many2one('mailing.mailing', string='Mass Mailing')
|
||||
mass_mailing_campaign_id = fields.Many2one('mail.mass_mailing.campaign', string='Mass Mailing Campaign')
|
||||
|
||||
|
||||
class LinkTrackerClick(models.Model):
|
||||
_inherit = "link.tracker.click"
|
||||
|
||||
mail_stat_id = fields.Many2one('mail.mail.statistics', string='Mail Statistics')
|
||||
mass_mailing_id = fields.Many2one('mail.mass_mailing', string='Mass Mailing')
|
||||
mailing_trace_id = fields.Many2one('mailing.trace', string='Mail Statistics')
|
||||
mass_mailing_id = fields.Many2one('mailing.mailing', string='Mass Mailing')
|
||||
mass_mailing_campaign_id = fields.Many2one('mail.mass_mailing.campaign', string='Mass Mailing Campaign')
|
||||
|
||||
def _prepare_click_values_from_route(self, **route_values):
|
||||
click_values = super(LinkTrackerClick, self)._prepare_click_values_from_route(**route_values)
|
||||
|
||||
if click_values.get('mail_stat_id'):
|
||||
stat_sudo = self.env['mail.mail.statistics'].sudo().browse(route_values['mail_stat_id']).exists()
|
||||
if not stat_sudo:
|
||||
click_values['mail_stat_id'] = False
|
||||
if click_values.get('mailing_trace_id'):
|
||||
trace_sudo = self.env['mailing.trace'].sudo().browse(route_values['mailing_trace_id']).exists()
|
||||
if not trace_sudo:
|
||||
click_values['mailing_trace_id'] = False
|
||||
else:
|
||||
if not click_values.get('mass_mailing_campaign_id'):
|
||||
click_values['mass_mailing_campaign_id'] = stat_sudo.mass_mailing_campaign_id.id
|
||||
click_values['mass_mailing_campaign_id'] = trace_sudo.mass_mailing_campaign_id.id
|
||||
if not click_values.get('mass_mailing_id'):
|
||||
click_values['mass_mailing_id'] = stat_sudo.mass_mailing_id.id
|
||||
click_values['mass_mailing_id'] = trace_sudo.mass_mailing_id.id
|
||||
|
||||
return click_values
|
||||
|
||||
@@ -37,8 +37,8 @@ class LinkTrackerClick(models.Model):
|
||||
def add_click(self, code, **route_values):
|
||||
click = super(LinkTrackerClick, self).add_click(code, **route_values)
|
||||
|
||||
if click and click.mail_stat_id:
|
||||
click.mail_stat_id.set_opened()
|
||||
click.mail_stat_id.set_clicked()
|
||||
if click and click.mailing_trace_id:
|
||||
click.mailing_trace_id.set_opened()
|
||||
click.mailing_trace_id.set_clicked()
|
||||
|
||||
return click
|
||||
|
||||
@@ -13,17 +13,17 @@ class MailMail(models.Model):
|
||||
"""Add the mass mailing campaign data to mail"""
|
||||
_inherit = ['mail.mail']
|
||||
|
||||
mailing_id = fields.Many2one('mail.mass_mailing', string='Mass Mailing')
|
||||
statistics_ids = fields.One2many('mail.mail.statistics', 'mail_mail_id', string='Statistics')
|
||||
mailing_id = fields.Many2one('mailing.mailing', string='Mass Mailing')
|
||||
mailing_trace_ids = fields.One2many('mailing.trace', 'mail_mail_id', string='Statistics')
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
""" Override mail_mail creation to create an entry in mail.mail.statistics """
|
||||
""" Override mail_mail creation to create an entry in mailing.trace """
|
||||
# TDE note: should be after 'all values computed', to have values (FIXME after merging other branch holding create refactoring)
|
||||
mail = super(MailMail, self).create(values)
|
||||
if values.get('statistics_ids'):
|
||||
if values.get('mailing_trace_ids'):
|
||||
mail_sudo = mail.sudo()
|
||||
mail_sudo.statistics_ids.write({'message_id': mail_sudo.message_id, 'state': 'outgoing'})
|
||||
mail_sudo.mailing_trace_ids.write({'message_id': mail_sudo.message_id, 'state': 'outgoing'})
|
||||
return mail
|
||||
|
||||
def _get_tracking_url(self):
|
||||
@@ -54,12 +54,12 @@ class MailMail(models.Model):
|
||||
|
||||
def _send_prepare_body(self):
|
||||
""" Override to add the tracking URL to the body and to add
|
||||
Statistic_id in shorted urls """
|
||||
trace ID in shortened urls """
|
||||
# TDE: temporary addition (mail was parameter) due to semi-new-API
|
||||
self.ensure_one()
|
||||
body = super(MailMail, self)._send_prepare_body()
|
||||
|
||||
if self.mailing_id and body and self.statistics_ids:
|
||||
if self.mailing_id and body and self.mailing_trace_ids:
|
||||
for match in re.findall(URL_REGEX, self.body_html):
|
||||
href = match[0]
|
||||
url = match[1]
|
||||
@@ -67,7 +67,7 @@ class MailMail(models.Model):
|
||||
parsed = werkzeug.urls.url_parse(url, scheme='http')
|
||||
|
||||
if parsed.scheme.startswith('http') and parsed.path.startswith('/r/'):
|
||||
new_href = href.replace(url, url + '/m/' + str(self.statistics_ids[0].id))
|
||||
new_href = href.replace(url, url + '/m/' + str(self.mailing_trace_ids[0].id))
|
||||
body = body.replace(href, new_href)
|
||||
|
||||
# generate tracking URL
|
||||
@@ -96,8 +96,8 @@ class MailMail(models.Model):
|
||||
mail_sent = not failure_type # we consider that a recipient error is a failure with mass mailling and show them as failed
|
||||
for mail in self:
|
||||
if mail.mailing_id:
|
||||
if mail_sent is True and mail.statistics_ids:
|
||||
mail.statistics_ids.write({'sent': fields.Datetime.now(), 'exception': False})
|
||||
elif mail_sent is False and mail.statistics_ids:
|
||||
mail.statistics_ids.write({'exception': fields.Datetime.now()})
|
||||
return super(MailMail, self)._postprocess_sent_message(success_pids, failure_reason=failure_reason, failure_type=failure_type)
|
||||
if mail_sent is True and mail.mailing_trace_ids:
|
||||
mail.mailing_trace_ids.write({'sent': fields.Datetime.now(), 'exception': False})
|
||||
elif mail_sent is False and mail.mailing_trace_ids:
|
||||
mail.mailing_trace_ids.write({'exception': fields.Datetime.now()})
|
||||
return super(MailMail, self)._postprocess_sent_message(success_pids, failure_reason=failure_reason, failure_type=failure_type)
|
||||
|
||||
@@ -13,12 +13,12 @@ BLACKLIST_MAX_BOUNCED_LIMIT = 5
|
||||
|
||||
|
||||
class MailThread(models.AbstractModel):
|
||||
""" Update MailThread to add the support of bounce management in mass mailing statistics. """
|
||||
""" Update MailThread to add the support of bounce management in mass mailing traces. """
|
||||
_inherit = 'mail.thread'
|
||||
|
||||
@api.model
|
||||
def message_route(self, message, message_dict, model=None, thread_id=None, custom_values=None):
|
||||
""" Override to udpate mass mailing statistics based on bounce emails """
|
||||
""" Override to udpate mass mailing traces based on bounce emails """
|
||||
bounce_alias = self.env['ir.config_parameter'].sudo().get_param("mail.bounce.alias")
|
||||
email_to = decode_message_header(message, 'To')
|
||||
email_to_localpart = (tools.email_split(email_to) or [''])[0].split('@', 1)[0].lower()
|
||||
@@ -28,19 +28,19 @@ class MailThread(models.AbstractModel):
|
||||
bounce_match = bounce_re.search(email_to)
|
||||
if bounce_match:
|
||||
bounced_mail_id = bounce_match.group(1)
|
||||
self.env['mail.mail.statistics'].set_bounced(mail_mail_ids=[bounced_mail_id])
|
||||
self.env['mailing.trace'].set_bounced(mail_mail_ids=[bounced_mail_id])
|
||||
|
||||
return super(MailThread, self).message_route(message, message_dict, model, thread_id, custom_values)
|
||||
|
||||
@api.model
|
||||
def message_route_process(self, message, message_dict, routes):
|
||||
""" Override to update the parent mail statistics. The parent is found
|
||||
""" Override to update the parent mailing traces. The parent is found
|
||||
by using the References header of the incoming message and looking for
|
||||
matching message_id in mail.mail.statistics. """
|
||||
matching message_id in mailing.trace. """
|
||||
if message.get('References') and routes:
|
||||
message_ids = [x.strip() for x in decode_smtp_header(message['References']).split()]
|
||||
self.env['mail.mail.statistics'].set_opened(mail_message_ids=message_ids)
|
||||
self.env['mail.mail.statistics'].set_replied(mail_message_ids=message_ids)
|
||||
self.env['mailing.trace'].set_opened(mail_message_ids=message_ids)
|
||||
self.env['mailing.trace'].set_replied(mail_message_ids=message_ids)
|
||||
return super(MailThread, self).message_route_process(message, message_dict, routes)
|
||||
|
||||
def message_post_with_template(self, template_id, **kwargs):
|
||||
@@ -64,11 +64,11 @@ class MailThread(models.AbstractModel):
|
||||
super(MailThread, self)._message_receive_bounce(email, partner, mail_id=None)
|
||||
|
||||
three_months_ago = fields.Datetime.to_string(datetime.datetime.now() - datetime.timedelta(weeks=13))
|
||||
stats = self.env['mail.mail.statistics'] \
|
||||
traces = self.env['mailing.trace'] \
|
||||
.search(['&', ('bounced', '>', three_months_ago), ('email', '=ilike', email)]) \
|
||||
.mapped('bounced')
|
||||
if len(stats) >= BLACKLIST_MAX_BOUNCED_LIMIT:
|
||||
if max(stats) > min(stats) + datetime.timedelta(weeks=1):
|
||||
if len(traces) >= BLACKLIST_MAX_BOUNCED_LIMIT:
|
||||
if max(traces) > min(traces) + datetime.timedelta(weeks=1):
|
||||
blacklist_rec = self.env['mail.blacklist'].sudo()._add(email)
|
||||
blacklist_rec._message_log(
|
||||
body='This email has been automatically blacklisted because of too much bounced.')
|
||||
|
||||
+162
-418
@@ -24,8 +24,8 @@ MASS_MAILING_BUSINESS_MODELS = [
|
||||
'res.partner',
|
||||
'event.track',
|
||||
'sale.order',
|
||||
'mail.mass_mailing.list',
|
||||
'mail.mass_mailing.contact'
|
||||
'mailing.list',
|
||||
'mailing.contact'
|
||||
]
|
||||
|
||||
# Syntax of the data URL Scheme: https://tools.ietf.org/html/rfc2397#section-3
|
||||
@@ -33,9 +33,9 @@ MASS_MAILING_BUSINESS_MODELS = [
|
||||
image_re = re.compile(r"data:(image/[A-Za-z]+);base64,(.*)")
|
||||
|
||||
|
||||
class MassMailingTag(models.Model):
|
||||
class MailingTag(models.Model):
|
||||
"""Model of categories of mass mailing, i.e. marketing, newsletter, ... """
|
||||
_name = 'mail.mass_mailing.tag'
|
||||
_name = 'mailing.tag'
|
||||
_description = 'Mass Mailing Tag'
|
||||
_order = 'name'
|
||||
|
||||
@@ -43,256 +43,14 @@ 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):
|
||||
class MailingStage(models.Model):
|
||||
|
||||
"""Stage for mass mailing campaigns. """
|
||||
_name = 'mail.mass_mailing.stage'
|
||||
_name = 'mailing.stage'
|
||||
_description = 'Mass Mailing Campaign Stage'
|
||||
_order = 'sequence'
|
||||
|
||||
@@ -307,8 +65,8 @@ class MassMailingCampaign(models.Model):
|
||||
_rec_name = "campaign_id"
|
||||
_inherits = {'utm.campaign': 'campaign_id'}
|
||||
|
||||
stage_id = fields.Many2one('mail.mass_mailing.stage', string='Stage', ondelete='restrict', required=True,
|
||||
default=lambda self: self.env['mail.mass_mailing.stage'].search([], limit=1),
|
||||
stage_id = fields.Many2one('mailing.stage', string='Stage', ondelete='restrict', required=True,
|
||||
default=lambda self: self.env['mailing.stage'].search([], limit=1),
|
||||
group_expand='_group_expand_stage_ids')
|
||||
user_id = fields.Many2one(
|
||||
'res.users', string='Responsible',
|
||||
@@ -320,10 +78,10 @@ class MassMailingCampaign(models.Model):
|
||||
medium_id = fields.Many2one('utm.medium', string='Medium',
|
||||
help="This is the delivery method, e.g. Postcard, Email, or Banner Ad", default=lambda self: self.env.ref('utm.utm_medium_email', False))
|
||||
tag_ids = fields.Many2many(
|
||||
'mail.mass_mailing.tag', 'mail_mass_mailing_tag_rel',
|
||||
'mailing.tag', 'mail_mass_mailing_tag_rel',
|
||||
'tag_id', 'campaign_id', string='Tags')
|
||||
mass_mailing_ids = fields.One2many(
|
||||
'mail.mass_mailing', 'mass_mailing_campaign_id',
|
||||
'mailing.mailing', 'mass_mailing_campaign_id',
|
||||
string='Mass Mailings')
|
||||
unique_ab_testing = fields.Boolean(string='Allow A/B Testing', default=False,
|
||||
help='If checked, recipients will be mailed only once for the whole campaign. '
|
||||
@@ -331,7 +89,7 @@ class MassMailingCampaign(models.Model):
|
||||
'the effectiveness of the mailings, without causing duplicate messages.')
|
||||
color = fields.Integer(string='Color Index')
|
||||
clicks_ratio = fields.Integer(compute="_compute_clicks_ratio", string="Number of clicks")
|
||||
# stat fields
|
||||
# trace statistics fields
|
||||
total = fields.Integer(compute="_compute_statistics")
|
||||
scheduled = fields.Integer(compute="_compute_statistics")
|
||||
failed = fields.Integer(compute="_compute_statistics")
|
||||
@@ -349,9 +107,9 @@ class MassMailingCampaign(models.Model):
|
||||
|
||||
def _compute_clicks_ratio(self):
|
||||
self.env.cr.execute("""
|
||||
SELECT COUNT(DISTINCT(stats.id)) AS nb_mails, COUNT(DISTINCT(clicks.mail_stat_id)) AS nb_clicks, stats.mass_mailing_campaign_id AS id
|
||||
FROM mail_mail_statistics AS stats
|
||||
LEFT OUTER JOIN link_tracker_click AS clicks ON clicks.mail_stat_id = stats.id
|
||||
SELECT COUNT(DISTINCT(stats.id)) AS nb_mails, COUNT(DISTINCT(clicks.mailing_trace_id)) AS nb_clicks, stats.mass_mailing_campaign_id AS id
|
||||
FROM mailing_trace AS stats
|
||||
LEFT OUTER JOIN link_tracker_click AS clicks ON clicks.mailing_trace_id = stats.id
|
||||
WHERE stats.mass_mailing_campaign_id IN %s
|
||||
GROUP BY stats.mass_mailing_campaign_id
|
||||
""", (tuple(self.ids), ))
|
||||
@@ -376,7 +134,7 @@ class MassMailingCampaign(models.Model):
|
||||
COUNT(CASE WHEN s.replied is not null THEN 1 ELSE null END) AS replied ,
|
||||
COUNT(CASE WHEN s.bounced is not null THEN 1 ELSE null END) AS bounced
|
||||
FROM
|
||||
mail_mail_statistics s
|
||||
mailing_trace s
|
||||
RIGHT JOIN
|
||||
mail_mass_mailing_campaign c
|
||||
ON (c.id = s.mass_mailing_campaign_id)
|
||||
@@ -396,14 +154,14 @@ class MassMailingCampaign(models.Model):
|
||||
self.browse(row.pop('campaign_id')).update(row)
|
||||
|
||||
def _compute_total_mailings(self):
|
||||
campaign_data = self.env['mail.mass_mailing'].read_group(
|
||||
campaign_data = self.env['mailing.mailing'].read_group(
|
||||
[('mass_mailing_campaign_id', 'in', self.ids)],
|
||||
['mass_mailing_campaign_id'], ['mass_mailing_campaign_id'])
|
||||
mapped_data = dict([(c['mass_mailing_campaign_id'][0], c['mass_mailing_campaign_id_count']) for c in campaign_data])
|
||||
for campaign in self:
|
||||
campaign.total_mailings = mapped_data.get(campaign.id, 0)
|
||||
|
||||
def get_recipients(self, model=None):
|
||||
def _get_recipients(self, model=None):
|
||||
"""Return the recipients of a mailing campaign. This is based on the statistics
|
||||
build for each mailing. """
|
||||
res = dict.fromkeys(self.ids, {})
|
||||
@@ -411,7 +169,7 @@ class MassMailingCampaign(models.Model):
|
||||
domain = [('mass_mailing_campaign_id', '=', campaign.id)]
|
||||
if model:
|
||||
domain += [('model', '=', model)]
|
||||
res[campaign.id] = set(self.env['mail.mail.statistics'].search(domain).mapped('res_id'))
|
||||
res[campaign.id] = set(self.env['mailing.trace'].search(domain).mapped('res_id'))
|
||||
return res
|
||||
|
||||
@api.model
|
||||
@@ -426,8 +184,7 @@ class MassMailingCampaign(models.Model):
|
||||
class MassMailing(models.Model):
|
||||
""" MassMailing models a wave of emails for a mass mailign campaign.
|
||||
A mass mailing is an occurence of sending emails. """
|
||||
|
||||
_name = 'mail.mass_mailing'
|
||||
_name = 'mailing.mailing'
|
||||
_description = 'Mass Mailing'
|
||||
# number of periods for tracking mail_mail statistics
|
||||
_period_number = 6
|
||||
@@ -448,7 +205,7 @@ class MassMailing(models.Model):
|
||||
def default_get(self, fields):
|
||||
res = super(MassMailing, self).default_get(fields)
|
||||
if 'reply_to_mode' in fields and not 'reply_to_mode' in res and res.get('mailing_model_real'):
|
||||
if res['mailing_model_real'] in ['res.partner', 'mail.mass_mailing.contact']:
|
||||
if res['mailing_model_real'] in ['res.partner', 'mailing.contact']:
|
||||
res['reply_to_mode'] = 'email'
|
||||
else:
|
||||
res['reply_to_mode'] = 'thread'
|
||||
@@ -471,8 +228,7 @@ class MassMailing(models.Model):
|
||||
help="This name helps you tracking your different campaign efforts, e.g. Fall_Drive, Christmas_Special")
|
||||
source_id = fields.Many2one('utm.source', string='Source', required=True, ondelete='cascade',
|
||||
help="This is the link source, e.g. Search Engine, another domain, or name of email list")
|
||||
medium_id = fields.Many2one('utm.medium', string='Medium',
|
||||
help="This is the delivery method, e.g. Postcard, Email, or Banner Ad", default=lambda self: self.env.ref('utm.utm_medium_email'))
|
||||
medium_id = fields.Many2one('utm.medium', string='Medium', help="Delivery method: Email")
|
||||
clicks_ratio = fields.Integer(compute="_compute_clicks_ratio", string="Number of Clicks")
|
||||
state = fields.Selection([('draft', 'Draft'), ('in_queue', 'In Queue'), ('sending', 'Sending'), ('done', 'Sent')],
|
||||
string='Status', required=True, copy=False, default='draft', group_expand='_group_expand_states')
|
||||
@@ -484,20 +240,20 @@ class MassMailing(models.Model):
|
||||
reply_to = fields.Char(string='Reply To', help='Preferred Reply-To Address',
|
||||
default=lambda self: self.env['mail.message']._get_default_from())
|
||||
# recipients
|
||||
mailing_model_real = fields.Char(compute='_compute_model', string='Recipients Real Model', default='mail.mass_mailing.contact', required=True)
|
||||
mailing_model_real = fields.Char(compute='_compute_model', string='Recipients Real Model', default='mailing.contact', required=True)
|
||||
mailing_model_id = fields.Many2one('ir.model', string='Recipients Model', domain=[('model', 'in', MASS_MAILING_BUSINESS_MODELS)],
|
||||
default=lambda self: self.env.ref('mass_mailing.model_mail_mass_mailing_list').id)
|
||||
default=lambda self: self.env.ref('mass_mailing.model_mailing_list').id)
|
||||
mailing_model_name = fields.Char(related='mailing_model_id.model', string='Recipients Model Name', readonly=True, related_sudo=True)
|
||||
mailing_domain = fields.Char(string='Domain', oldname='domain', default=[])
|
||||
mail_server_id = fields.Many2one('ir.mail_server', string='Mail Server',
|
||||
default=_get_default_mail_server_id,
|
||||
help="Use a specific mail server in priority. Otherwise Odoo relies on the first outgoing mail server available (based on their sequencing) as it does for normal mails.")
|
||||
contact_list_ids = fields.Many2many('mail.mass_mailing.list', 'mail_mass_mailing_list_rel',
|
||||
contact_list_ids = fields.Many2many('mailing.list', 'mail_mass_mailing_list_rel',
|
||||
string='Mailing Lists')
|
||||
contact_ab_pc = fields.Integer(string='A/B Testing percentage',
|
||||
help='Percentage of the contacts that will be mailed. Recipients will be taken randomly.', default=100)
|
||||
# statistics data
|
||||
statistics_ids = fields.One2many('mail.mail.statistics', 'mass_mailing_id', string='Emails Statistics')
|
||||
mailing_trace_ids = fields.One2many('mailing.trace', 'mass_mailing_id', string='Emails Statistics')
|
||||
total = fields.Integer(compute="_compute_total")
|
||||
scheduled = fields.Integer(compute="_compute_statistics")
|
||||
expected = fields.Integer(compute="_compute_statistics")
|
||||
@@ -517,13 +273,13 @@ class MassMailing(models.Model):
|
||||
|
||||
def _compute_total(self):
|
||||
for mass_mailing in self:
|
||||
mass_mailing.total = len(mass_mailing.sudo().get_recipients())
|
||||
mass_mailing.total = len(mass_mailing.sudo()._get_recipients())
|
||||
|
||||
def _compute_clicks_ratio(self):
|
||||
self.env.cr.execute("""
|
||||
SELECT COUNT(DISTINCT(stats.id)) AS nb_mails, COUNT(DISTINCT(clicks.mail_stat_id)) AS nb_clicks, stats.mass_mailing_id AS id
|
||||
FROM mail_mail_statistics AS stats
|
||||
LEFT OUTER JOIN link_tracker_click AS clicks ON clicks.mail_stat_id = stats.id
|
||||
SELECT COUNT(DISTINCT(stats.id)) AS nb_mails, COUNT(DISTINCT(clicks.mailing_trace_id)) AS nb_clicks, stats.mass_mailing_id AS id
|
||||
FROM mailing_trace AS stats
|
||||
LEFT OUTER JOIN link_tracker_click AS clicks ON clicks.mailing_trace_id = stats.id
|
||||
WHERE stats.mass_mailing_id IN %s
|
||||
GROUP BY stats.mass_mailing_id
|
||||
""", (tuple(self.ids), ))
|
||||
@@ -536,7 +292,7 @@ class MassMailing(models.Model):
|
||||
@api.depends('mailing_model_id')
|
||||
def _compute_model(self):
|
||||
for record in self:
|
||||
record.mailing_model_real = (record.mailing_model_name != 'mail.mass_mailing.list') and record.mailing_model_name or 'mail.mass_mailing.contact'
|
||||
record.mailing_model_real = (record.mailing_model_name != 'mailing.list') and record.mailing_model_name or 'mailing.contact'
|
||||
|
||||
def _compute_statistics(self):
|
||||
""" Compute statistics of the mass mailing """
|
||||
@@ -555,9 +311,9 @@ class MassMailing(models.Model):
|
||||
COUNT(CASE WHEN s.bounced is not null THEN 1 ELSE null END) AS bounced,
|
||||
COUNT(CASE WHEN s.exception is not null THEN 1 ELSE null END) AS failed
|
||||
FROM
|
||||
mail_mail_statistics s
|
||||
mailing_trace s
|
||||
RIGHT JOIN
|
||||
mail_mass_mailing m
|
||||
mailing_mailing m
|
||||
ON (m.id = s.mass_mailing_id)
|
||||
WHERE
|
||||
m.id IN %s
|
||||
@@ -573,24 +329,6 @@ class MassMailing(models.Model):
|
||||
row['bounced_ratio'] = 100.0 * row['bounced'] / total
|
||||
self.browse(row.pop('mailing_id')).update(row)
|
||||
|
||||
def _unsubscribe_token(self, res_id, email):
|
||||
"""Generate a secure hash for this mailing list and parameters.
|
||||
|
||||
This is appended to the unsubscription URL and then checked at
|
||||
unsubscription time to ensure no malicious unsubscriptions are
|
||||
performed.
|
||||
|
||||
:param int res_id:
|
||||
ID of the resource that will be unsubscribed.
|
||||
|
||||
:param str email:
|
||||
Email of the resource that will be unsubscribed.
|
||||
"""
|
||||
secret = self.env["ir.config_parameter"].sudo().get_param(
|
||||
"database.secret")
|
||||
token = (self.env.cr.dbname, self.id, int(res_id), tools.ustr(email))
|
||||
return hmac.new(secret.encode('utf-8'), repr(token).encode('utf-8'), hashlib.sha512).hexdigest()
|
||||
|
||||
def _compute_next_departure(self):
|
||||
cron_next_call = self.env.ref('mass_mailing.ir_cron_mass_mailing_queue').sudo().nextcall
|
||||
str2dt = fields.Datetime.from_string
|
||||
@@ -605,16 +343,13 @@ class MassMailing(models.Model):
|
||||
@api.onchange('mass_mailing_campaign_id')
|
||||
def _onchange_mass_mailing_campaign_id(self):
|
||||
if self.mass_mailing_campaign_id:
|
||||
dic = {'campaign_id': self.mass_mailing_campaign_id.campaign_id,
|
||||
'source_id': self.mass_mailing_campaign_id.source_id,
|
||||
'medium_id': self.mass_mailing_campaign_id.medium_id}
|
||||
self.update(dic)
|
||||
self.campaign_id = self.mass_mailing_campaign_id.campaign_id.id
|
||||
|
||||
@api.onchange('mailing_model_id', 'contact_list_ids')
|
||||
def _onchange_model_and_list(self):
|
||||
mailing_domain = []
|
||||
if self.mailing_model_name:
|
||||
if self.mailing_model_name == 'mail.mass_mailing.list':
|
||||
if self.mailing_model_name == 'mailing.list':
|
||||
if self.contact_list_ids:
|
||||
mailing_domain.append(('list_ids', 'in', self.contact_list_ids.ids))
|
||||
else:
|
||||
@@ -632,21 +367,24 @@ class MassMailing(models.Model):
|
||||
if self.subject and not self.name:
|
||||
self.name = self.subject
|
||||
|
||||
#------------------------------------------------------
|
||||
# Technical stuff
|
||||
#------------------------------------------------------
|
||||
# ------------------------------------------------------
|
||||
# ORM
|
||||
# ------------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def name_create(self, name):
|
||||
""" _rec_name is source_id, creates a utm.source instead """
|
||||
mass_mailing = self.create({'name': name, 'subject': name})
|
||||
return mass_mailing.name_get()[0]
|
||||
def create(self, values):
|
||||
if values.get('name') and not values.get('subject'):
|
||||
values['subject'] = values['name']
|
||||
if values.get('body_html'):
|
||||
values['body_html'] = self._convert_inline_images_to_urls(values['body_html'])
|
||||
if 'medium_id' not in values:
|
||||
values['medium_id'] = self.env.ref('utm.utm_medium_email').id
|
||||
return super(MassMailing, self).create(values)
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if vals.get('name') and not vals.get('subject'):
|
||||
vals['subject'] = vals['name']
|
||||
return super(MassMailing, self).create(vals)
|
||||
def write(self, values):
|
||||
if values.get('body_html'):
|
||||
values['body_html'] = self._convert_inline_images_to_urls(values['body_html'])
|
||||
return super(MassMailing, self).write(values)
|
||||
|
||||
@api.returns('self', lambda value: value.id)
|
||||
def copy(self, default=None):
|
||||
@@ -658,74 +396,9 @@ class MassMailing(models.Model):
|
||||
def _group_expand_states(self, states, domain, order):
|
||||
return [key for key, val in type(self).state.selection]
|
||||
|
||||
def update_opt_out(self, email, list_ids, value):
|
||||
if len(list_ids) > 0:
|
||||
model = self.env['mail.mass_mailing.contact'].with_context(active_test=False)
|
||||
records = model.search([('email_normalized', '=', tools.email_normalize(email))])
|
||||
opt_out_records = self.env['mail.mass_mailing.list_contact_rel'].search([
|
||||
('contact_id', 'in', records.ids),
|
||||
('list_id', 'in', list_ids),
|
||||
('opt_out', '!=', value)
|
||||
])
|
||||
|
||||
opt_out_records.write({'opt_out': value})
|
||||
message = _('The recipient <strong>unsubscribed from %s</strong> mailing list(s)') \
|
||||
if value else _('The recipient <strong>subscribed to %s</strong> mailing list(s)')
|
||||
for record in records:
|
||||
# filter the list_id by record
|
||||
record_lists = opt_out_records.filtered(lambda rec: rec.contact_id.id == record.id)
|
||||
if len(record_lists) > 0:
|
||||
record.sudo().message_post(body=_(message % ', '.join(str(list.name) for list in record_lists.mapped('list_id'))))
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
if values.get('body_html'):
|
||||
values['body_html'] = self._convert_inline_images_to_urls(values['body_html'])
|
||||
return super(MassMailing, self).create(values)
|
||||
|
||||
def write(self, values):
|
||||
if values.get('body_html'):
|
||||
values['body_html'] = self._convert_inline_images_to_urls(values['body_html'])
|
||||
return super(MassMailing, self).write(values)
|
||||
|
||||
def _convert_inline_images_to_urls(self, body_html):
|
||||
"""
|
||||
Find inline base64 encoded images, make an attachement out of
|
||||
them and replace the inline image with an url to the attachement.
|
||||
"""
|
||||
|
||||
def _image_to_url(b64image: bytes):
|
||||
"""Store an image in an attachement and returns an url"""
|
||||
attachment = self.env['ir.attachment'].create({
|
||||
'datas': b64image,
|
||||
'name': "cropped_image_mailing_{}".format(self.id),
|
||||
'type': 'binary',})
|
||||
|
||||
attachment.generate_access_token()
|
||||
|
||||
return '/web/image/%s?access_token=%s' % (
|
||||
attachment.id, attachment.access_token)
|
||||
|
||||
|
||||
modified = False
|
||||
root = lxml.html.fromstring(body_html)
|
||||
for node in root.iter('img'):
|
||||
match = image_re.match(node.attrib.get('src', ''))
|
||||
if match:
|
||||
mime = match.group(1) # unsed
|
||||
image = match.group(2).encode() # base64 image as bytes
|
||||
|
||||
node.attrib['src'] = _image_to_url(image)
|
||||
modified = True
|
||||
|
||||
if modified:
|
||||
return lxml.html.tostring(root)
|
||||
|
||||
return body_html
|
||||
|
||||
#------------------------------------------------------
|
||||
# Views & Actions
|
||||
#------------------------------------------------------
|
||||
# ------------------------------------------------------
|
||||
# ACTIONS
|
||||
# ------------------------------------------------------
|
||||
|
||||
def action_duplicate(self):
|
||||
self.ensure_one()
|
||||
@@ -736,7 +409,7 @@ class MassMailing(models.Model):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'mail.mass_mailing',
|
||||
'res_model': 'mailing.mailing',
|
||||
'res_id': mass_mailing_copy.id,
|
||||
'context': context,
|
||||
}
|
||||
@@ -749,14 +422,14 @@ class MassMailing(models.Model):
|
||||
'name': _('Test Mailing'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'mail.mass_mailing.test',
|
||||
'res_model': 'mailing.mailing.test',
|
||||
'target': 'new',
|
||||
'context': ctx,
|
||||
}
|
||||
|
||||
def action_schedule_date(self):
|
||||
self.ensure_one()
|
||||
action = self.env.ref('mass_mailing.mass_mailing_schedule_date_action').read()[0]
|
||||
action = self.env.ref('mass_mailing.mailing_mailing_schedule_date_action').read()[0]
|
||||
action['context'] = dict(self.env.context, default_mass_mailing_id=self.id)
|
||||
return action
|
||||
|
||||
@@ -768,10 +441,10 @@ class MassMailing(models.Model):
|
||||
|
||||
def retry_failed_mail(self):
|
||||
failed_mails = self.env['mail.mail'].search([('mailing_id', 'in', self.ids), ('state', '=', 'exception')])
|
||||
failed_mails.mapped('statistics_ids').unlink()
|
||||
failed_mails.mapped('mailing_trace_ids').unlink()
|
||||
failed_mails.sudo().unlink()
|
||||
res_ids = self.get_recipients()
|
||||
except_mailed = self.env['mail.mail.statistics'].search([
|
||||
res_ids = self._get_recipients()
|
||||
except_mailed = self.env['mailing.trace'].search([
|
||||
('model', '=', self.mailing_model_real),
|
||||
('res_id', 'in', res_ids),
|
||||
('exception', '!=', False),
|
||||
@@ -798,11 +471,11 @@ class MassMailing(models.Model):
|
||||
|
||||
def _action_view_documents_filtered(self, view_filter):
|
||||
if view_filter in ('sent', 'opened', 'replied', 'bounced', 'clicked'):
|
||||
opened_stats = self.statistics_ids.filtered(lambda stat: stat[view_filter])
|
||||
opened_stats = self.mailing_trace_ids.filtered(lambda stat: stat[view_filter])
|
||||
elif view_filter == ('delivered'):
|
||||
opened_stats = self.statistics_ids.filtered(lambda stat: stat.sent and not stat.bounced)
|
||||
opened_stats = self.mailing_trace_ids.filtered(lambda stat: stat.sent and not stat.bounced)
|
||||
else:
|
||||
opened_stats = self.env['mail.mail.statistics']
|
||||
opened_stats = self.env['mailing.trace']
|
||||
res_ids = opened_stats.mapped('res_id')
|
||||
model_name = self.env['ir.model']._get(self.mailing_model_real).display_name
|
||||
return {
|
||||
@@ -813,20 +486,39 @@ class MassMailing(models.Model):
|
||||
'domain': [('id', 'in', res_ids)],
|
||||
}
|
||||
|
||||
#------------------------------------------------------
|
||||
def update_opt_out(self, email, list_ids, value):
|
||||
if len(list_ids) > 0:
|
||||
model = self.env['mailing.contact'].with_context(active_test=False)
|
||||
records = model.search([('email_normalized', '=', tools.email_normalize(email))])
|
||||
opt_out_records = self.env['mailing.contact.subscription'].search([
|
||||
('contact_id', 'in', records.ids),
|
||||
('list_id', 'in', list_ids),
|
||||
('opt_out', '!=', value)
|
||||
])
|
||||
|
||||
opt_out_records.write({'opt_out': value})
|
||||
message = _('The recipient <strong>unsubscribed from %s</strong> mailing list(s)') \
|
||||
if value else _('The recipient <strong>subscribed to %s</strong> mailing list(s)')
|
||||
for record in records:
|
||||
# filter the list_id by record
|
||||
record_lists = opt_out_records.filtered(lambda rec: rec.contact_id.id == record.id)
|
||||
if len(record_lists) > 0:
|
||||
record.sudo().message_post(body=_(message % ', '.join(str(list.name) for list in record_lists.mapped('list_id'))))
|
||||
|
||||
# ------------------------------------------------------
|
||||
# Email Sending
|
||||
#------------------------------------------------------
|
||||
# ------------------------------------------------------
|
||||
|
||||
def _get_opt_out_list(self):
|
||||
"""Returns a set of emails opted-out in target model"""
|
||||
self.ensure_one()
|
||||
opt_out = {}
|
||||
target = self.env[self.mailing_model_real]
|
||||
if self.mailing_model_real == "mail.mass_mailing.contact":
|
||||
if self.mailing_model_real == "mailing.contact":
|
||||
# if user is opt_out on One list but not on another
|
||||
# or if two user with same email address, one opted in and the other one opted out, send the mail anyway
|
||||
# TODO DBE Fixme : Optimise the following to get real opt_out and opt_in
|
||||
target_list_contacts = self.env['mail.mass_mailing.list_contact_rel'].search(
|
||||
target_list_contacts = self.env['mailing.contact.subscription'].search(
|
||||
[('list_id', 'in', self.contact_list_ids.ids)])
|
||||
opt_out_contacts = target_list_contacts.filtered(lambda rel: rel.opt_out).mapped('contact_id.email_normalized')
|
||||
opt_in_contacts = target_list_contacts.filtered(lambda rel: not rel.opt_out).mapped('contact_id.email_normalized')
|
||||
@@ -840,18 +532,14 @@ class MassMailing(models.Model):
|
||||
return opt_out
|
||||
|
||||
def _get_convert_links(self):
|
||||
self.ensure_one()
|
||||
utm_mixin = self.mass_mailing_campaign_id if self.mass_mailing_campaign_id else self
|
||||
vals = {'mass_mailing_id': self.id}
|
||||
|
||||
vals = {
|
||||
'mass_mailing_id': self.id,
|
||||
'source_id': self.source_id.id,
|
||||
'medium_id': self.medium_id.id,
|
||||
}
|
||||
if self.mass_mailing_campaign_id:
|
||||
vals['mass_mailing_campaign_id'] = self.mass_mailing_campaign_id.id
|
||||
if utm_mixin.campaign_id:
|
||||
vals['campaign_id'] = utm_mixin.campaign_id.id
|
||||
if utm_mixin.source_id:
|
||||
vals['source_id'] = utm_mixin.source_id.id
|
||||
if utm_mixin.medium_id:
|
||||
vals['medium_id'] = utm_mixin.medium_id.id
|
||||
vals['campaign_id'] = self.mass_mailing_campaign_id.campaign_id.id
|
||||
return vals
|
||||
|
||||
def _get_seen_list(self):
|
||||
@@ -863,7 +551,7 @@ class MassMailing(models.Model):
|
||||
# + use a basic heuristic for extracting emails
|
||||
query = """
|
||||
SELECT lower(substring(t.%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)'))
|
||||
FROM mail_mail_statistics s
|
||||
FROM mailing_trace s
|
||||
JOIN %(target)s t ON (s.res_id = t.id)
|
||||
WHERE substring(t.%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)') IS NOT NULL
|
||||
"""
|
||||
@@ -873,7 +561,7 @@ class MassMailing(models.Model):
|
||||
mail_field = 'email'
|
||||
query = """
|
||||
SELECT lower(substring(p.%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)'))
|
||||
FROM mail_mail_statistics s
|
||||
FROM mailing_trace s
|
||||
JOIN %(target)s t ON (s.res_id = t.id)
|
||||
JOIN res_partner p ON (t.partner_id = p.id)
|
||||
WHERE substring(p.%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)') IS NOT NULL
|
||||
@@ -914,7 +602,7 @@ class MassMailing(models.Model):
|
||||
'post_convert_links': self._get_convert_links(),
|
||||
}
|
||||
|
||||
def get_recipients(self):
|
||||
def _get_recipients(self):
|
||||
if self.mailing_domain:
|
||||
domain = safe_eval(self.mailing_domain)
|
||||
res_ids = self.env[self.mailing_model_real].search(domain).ids
|
||||
@@ -927,7 +615,7 @@ class MassMailing(models.Model):
|
||||
contact_nbr = self.env[self.mailing_model_real].search_count(domain)
|
||||
topick = int(contact_nbr / 100.0 * self.contact_ab_pc)
|
||||
if self.mass_mailing_campaign_id and self.mass_mailing_campaign_id.unique_ab_testing:
|
||||
already_mailed = self.mass_mailing_campaign_id.get_recipients()[self.mass_mailing_campaign_id.id]
|
||||
already_mailed = self.mass_mailing_campaign_id._get_recipients()[self.mass_mailing_campaign_id.id]
|
||||
else:
|
||||
already_mailed = set([])
|
||||
remaining = set(res_ids).difference(already_mailed)
|
||||
@@ -936,20 +624,21 @@ class MassMailing(models.Model):
|
||||
res_ids = random.sample(remaining, topick)
|
||||
return res_ids
|
||||
|
||||
def get_remaining_recipients(self):
|
||||
res_ids = self.get_recipients()
|
||||
already_mailed = self.env['mail.mail.statistics'].search_read([('model', '=', self.mailing_model_real),
|
||||
('res_id', 'in', res_ids),
|
||||
('mass_mailing_id', '=', self.id)], ['res_id'])
|
||||
already_mailed_res_ids = [record['res_id'] for record in already_mailed]
|
||||
return list(set(res_ids) - set(already_mailed_res_ids))
|
||||
def _get_remaining_recipients(self):
|
||||
res_ids = self._get_recipients()
|
||||
already_mailed = self.env['mailing.trace'].search_read([
|
||||
('model', '=', self.mailing_model_real),
|
||||
('res_id', 'in', res_ids),
|
||||
('mass_mailing_id', '=', self.id)], ['res_id'])
|
||||
done_res_ids = [record['res_id'] for record in already_mailed]
|
||||
return [rid for rid in res_ids if rid not in done_res_ids]
|
||||
|
||||
def send_mail(self, res_ids=None):
|
||||
def action_send_mail(self, res_ids=None):
|
||||
author_id = self.env.user.partner_id.id
|
||||
|
||||
for mailing in self:
|
||||
if not res_ids:
|
||||
res_ids = mailing.get_remaining_recipients()
|
||||
res_ids = mailing._get_remaining_recipients()
|
||||
if not res_ids:
|
||||
raise UserError(_('There is no recipients selected.'))
|
||||
|
||||
@@ -1007,8 +696,63 @@ class MassMailing(models.Model):
|
||||
for mass_mailing in mass_mailings:
|
||||
user = mass_mailing.write_uid or self.env.user
|
||||
mass_mailing = mass_mailing.with_context(**user.with_user(user).context_get())
|
||||
if len(mass_mailing.get_remaining_recipients()) > 0:
|
||||
if len(mass_mailing._get_remaining_recipients()) > 0:
|
||||
mass_mailing.state = 'sending'
|
||||
mass_mailing.send_mail()
|
||||
mass_mailing.action_send_mail()
|
||||
else:
|
||||
mass_mailing.write({'state': 'done', 'sent_date': fields.Datetime.now()})
|
||||
|
||||
# ------------------------------------------------------
|
||||
# TOOLS
|
||||
# ------------------------------------------------------
|
||||
|
||||
def _unsubscribe_token(self, res_id, email):
|
||||
"""Generate a secure hash for this mailing list and parameters.
|
||||
|
||||
This is appended to the unsubscription URL and then checked at
|
||||
unsubscription time to ensure no malicious unsubscriptions are
|
||||
performed.
|
||||
|
||||
:param int res_id:
|
||||
ID of the resource that will be unsubscribed.
|
||||
|
||||
:param str email:
|
||||
Email of the resource that will be unsubscribed.
|
||||
"""
|
||||
secret = self.env["ir.config_parameter"].sudo().get_param("database.secret")
|
||||
token = (self.env.cr.dbname, self.id, int(res_id), tools.ustr(email))
|
||||
return hmac.new(secret.encode('utf-8'), repr(token).encode('utf-8'), hashlib.sha512).hexdigest()
|
||||
|
||||
def _convert_inline_images_to_urls(self, body_html):
|
||||
"""
|
||||
Find inline base64 encoded images, make an attachement out of
|
||||
them and replace the inline image with an url to the attachement.
|
||||
"""
|
||||
|
||||
def _image_to_url(b64image: bytes):
|
||||
"""Store an image in an attachement and returns an url"""
|
||||
attachment = self.env['ir.attachment'].create({
|
||||
'datas': b64image,
|
||||
'name': "cropped_image_mailing_{}".format(self.id),
|
||||
'type': 'binary',})
|
||||
|
||||
attachment.generate_access_token()
|
||||
|
||||
return '/web/image/%s?access_token=%s' % (
|
||||
attachment.id, attachment.access_token)
|
||||
|
||||
modified = False
|
||||
root = lxml.html.fromstring(body_html)
|
||||
for node in root.iter('img'):
|
||||
match = image_re.match(node.attrib.get('src', ''))
|
||||
if match:
|
||||
mime = match.group(1) # unsed
|
||||
image = match.group(2).encode() # base64 image as bytes
|
||||
|
||||
node.attrib['src'] = _image_to_url(image)
|
||||
modified = True
|
||||
|
||||
if modified:
|
||||
return lxml.html.tostring(root)
|
||||
|
||||
return body_html
|
||||
@@ -0,0 +1,141 @@
|
||||
# -*- 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 = 'mailing.contact.subscription'
|
||||
_description = 'Mass Mailing Subscription Information'
|
||||
_table = 'mailing_contact_list_rel'
|
||||
_rec_name = 'contact_id'
|
||||
|
||||
contact_id = fields.Many2one('mailing.contact', string='Contact', ondelete='cascade', required=True)
|
||||
list_id = fields.Many2one('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': '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 = '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(
|
||||
'mailing.list', 'mailing_contact_list_rel',
|
||||
'contact_id', 'list_id', string='Mailing Lists')
|
||||
subscription_list_ids = fields.One2many('mailing.contact.subscription', '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['mailing.contact.subscription'].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 = '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(
|
||||
'mailing.contact', 'mailing_contact_list_rel', 'list_id', 'contact_id',
|
||||
string='Mailing Lists')
|
||||
subscription_ids = fields.One2many('mailing.contact.subscription', '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
|
||||
mailing_contact_list_rel r
|
||||
left join 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 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,
|
||||
list.id AS list_id,
|
||||
row_number() OVER (PARTITION BY email ORDER BY email) AS rn
|
||||
FROM
|
||||
mailing_contact contact,
|
||||
mailing_contact_list_rel contact_list_rel,
|
||||
mailing_list 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 list.id=contact_list_rel.list_id
|
||||
AND list.id IN %s
|
||||
AND NOT EXISTS
|
||||
(
|
||||
SELECT 1
|
||||
FROM
|
||||
mailing_contact contact2,
|
||||
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'}
|
||||
+24
-24
@@ -4,17 +4,17 @@
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class MailMailStats(models.Model):
|
||||
""" MailMailStats models the statistics collected about emails. Those statistics
|
||||
class MailingTrace(models.Model):
|
||||
""" MailingTrace models the statistics collected about emails. Those statistics
|
||||
are stored in a separated model and table to avoid bloating the mail_mail table
|
||||
with statistics values. This also allows to delete emails send with mass mailing
|
||||
without loosing the statistics about them. """
|
||||
|
||||
_name = 'mail.mail.statistics'
|
||||
_description = 'Email Statistics'
|
||||
_name = 'mailing.trace'
|
||||
_description = 'Mailing Statistics'
|
||||
_rec_name = 'message_id'
|
||||
_order = 'message_id'
|
||||
|
||||
# mail data
|
||||
mail_mail_id = fields.Many2one('mail.mail', string='Mail', index=True)
|
||||
mail_mail_id_int = fields.Integer(
|
||||
string='Mail ID (tech)',
|
||||
@@ -23,11 +23,13 @@ class MailMailStats(models.Model):
|
||||
'However the ID is needed for several action and controllers.',
|
||||
index=True,
|
||||
)
|
||||
email = fields.Char(string="Recipient email address")
|
||||
message_id = fields.Char(string='Message-ID')
|
||||
# document
|
||||
model = fields.Char(string='Document model')
|
||||
res_id = fields.Integer(string='Document ID')
|
||||
# campaign / wave data
|
||||
mass_mailing_id = fields.Many2one('mail.mass_mailing', string='Mass Mailing', index=True)
|
||||
mass_mailing_id = fields.Many2one('mailing.mailing', string='Mass Mailing', index=True)
|
||||
mass_mailing_campaign_id = fields.Many2one(
|
||||
related='mass_mailing_id.mass_mailing_campaign_id',
|
||||
string='Mass Mailing Campaign',
|
||||
@@ -42,7 +44,7 @@ class MailMailStats(models.Model):
|
||||
replied = fields.Datetime(help='Date when this email has been replied for the first time.')
|
||||
bounced = fields.Datetime(help='Date when this email has bounced.')
|
||||
# Link tracking
|
||||
links_click_ids = fields.One2many('link.tracker.click', 'mail_stat_id', string='Links click')
|
||||
links_click_ids = fields.One2many('link.tracker.click', 'mailing_trace_id', string='Links click')
|
||||
clicked = fields.Datetime(help='Date when customer clicked on at least one tracked link')
|
||||
# Status
|
||||
state = fields.Selection(compute="_compute_state",
|
||||
@@ -54,9 +56,8 @@ class MailMailStats(models.Model):
|
||||
('bounced', 'Bounced'),
|
||||
('ignored', 'Ignored')], store=True)
|
||||
state_update = fields.Datetime(compute="_compute_state", string='State Update',
|
||||
help='Last state update of the mail',
|
||||
store=True)
|
||||
email = fields.Char(string="Recipient email address")
|
||||
help='Last state update of the mail',
|
||||
store=True)
|
||||
|
||||
@api.depends('sent', 'opened', 'clicked', 'replied', 'bounced', 'exception', 'ignored')
|
||||
def _compute_state(self):
|
||||
@@ -81,7 +82,7 @@ class MailMailStats(models.Model):
|
||||
def create(self, values):
|
||||
if 'mail_mail_id' in values:
|
||||
values['mail_mail_id_int'] = values['mail_mail_id']
|
||||
res = super(MailMailStats, self).create(values)
|
||||
res = super(MailingTrace, self).create(values)
|
||||
return res
|
||||
|
||||
def _get_records(self, mail_mail_ids=None, mail_message_ids=None, domain=None):
|
||||
@@ -96,22 +97,21 @@ class MailMailStats(models.Model):
|
||||
return self.search(base_domain)
|
||||
|
||||
def set_opened(self, mail_mail_ids=None, mail_message_ids=None):
|
||||
statistics = self._get_records(mail_mail_ids, mail_message_ids, [('opened', '=', False)])
|
||||
statistics.write({'opened': fields.Datetime.now(), 'bounced': False})
|
||||
return statistics
|
||||
traces = self._get_records(mail_mail_ids, mail_message_ids, [('opened', '=', False)])
|
||||
traces.write({'opened': fields.Datetime.now(), 'bounced': False})
|
||||
return traces
|
||||
|
||||
def set_clicked(self, mail_mail_ids=None, mail_message_ids=None):
|
||||
statistics = self._get_records(mail_mail_ids, mail_message_ids, [('clicked', '=', False)])
|
||||
statistics.write({'clicked': fields.Datetime.now()})
|
||||
return statistics
|
||||
traces = self._get_records(mail_mail_ids, mail_message_ids, [('clicked', '=', False)])
|
||||
traces.write({'clicked': fields.Datetime.now()})
|
||||
return traces
|
||||
|
||||
def set_replied(self, mail_mail_ids=None, mail_message_ids=None):
|
||||
statistics = self._get_records(mail_mail_ids, mail_message_ids, [('replied', '=', False)])
|
||||
statistics.write({'replied': fields.Datetime.now()})
|
||||
return statistics
|
||||
traces = self._get_records(mail_mail_ids, mail_message_ids, [('replied', '=', False)])
|
||||
traces.write({'replied': fields.Datetime.now()})
|
||||
return traces
|
||||
|
||||
def set_bounced(self, mail_mail_ids=None, mail_message_ids=None):
|
||||
statistics = self._get_records(
|
||||
mail_mail_ids, mail_message_ids, [('bounced', '=', False), ('opened', '=', False)])
|
||||
statistics.write({'bounced': fields.Datetime.now()})
|
||||
return statistics
|
||||
traces = self._get_records(mail_mail_ids, mail_message_ids, [('bounced', '=', False), ('opened', '=', False)])
|
||||
traces.write({'bounced': fields.Datetime.now()})
|
||||
return traces
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import mailing_trace_report
|
||||
+10
-10
@@ -1,15 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, tools
|
||||
from odoo import fields, models, tools
|
||||
|
||||
|
||||
class MassMailingReport(models.Model):
|
||||
_name = 'mail.statistics.report'
|
||||
class MailingTraceReport(models.Model):
|
||||
_name = 'mailing.trace.report'
|
||||
_auto = False
|
||||
_description = 'Mass Mailing Statistics'
|
||||
|
||||
scheduled_date = fields.Datetime(stirng='Scheduled Date', readonly=True)
|
||||
scheduled_date = fields.Datetime(string='Scheduled Date', readonly=True)
|
||||
name = fields.Char(string='Mass Mail', readonly=True)
|
||||
campaign = fields.Char(string='Mass Mail Campaign', readonly=True)
|
||||
sent = fields.Integer(readonly=True)
|
||||
@@ -23,12 +23,12 @@ class MassMailingReport(models.Model):
|
||||
email_from = fields.Char('From', readonly=True)
|
||||
|
||||
def init(self):
|
||||
"""Mass Mail Statistical Report: based on mail.mail.statistics that models the various
|
||||
statistics collected for each mailing, and mail.mass_mailing model that models the
|
||||
"""Mass Mail Statistical Report: based on mailing.trace that models the various
|
||||
statistics collected for each mailing, and mailing.mailing model that models the
|
||||
various mailing performed. """
|
||||
tools.drop_view_if_exists(self.env.cr, 'mail_statistics_report')
|
||||
tools.drop_view_if_exists(self.env.cr, 'mailing_trace_report')
|
||||
self.env.cr.execute("""
|
||||
CREATE OR REPLACE VIEW mail_statistics_report AS (
|
||||
CREATE OR REPLACE VIEW mailing_trace_report AS (
|
||||
SELECT
|
||||
min(ms.id) as id,
|
||||
ms.scheduled as scheduled_date,
|
||||
@@ -43,8 +43,8 @@ class MassMailingReport(models.Model):
|
||||
mm.state,
|
||||
mm.email_from
|
||||
FROM
|
||||
mail_mail_statistics as ms
|
||||
left join mail_mass_mailing as mm ON (ms.mass_mailing_id=mm.id)
|
||||
mailing_trace as ms
|
||||
left join mailing_mailing as mm ON (ms.mass_mailing_id=mm.id)
|
||||
left join mail_mass_mailing_campaign as mc ON (ms.mass_mailing_campaign_id=mc.id)
|
||||
left join utm_campaign as utm_campaign ON (mc.campaign_id = utm_campaign.id)
|
||||
left join utm_source as utm_source ON (mm.source_id = utm_source.id)
|
||||
+12
-12
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_mail_statistics_report_pivot" model="ir.ui.view">
|
||||
<field name="name">mail.statistics.report.pivot</field>
|
||||
<field name="model">mail.statistics.report</field>
|
||||
<record id="mailing_trace_report_view_pivot" model="ir.ui.view">
|
||||
<field name="name">mailing.trace.report.view.pivot</field>
|
||||
<field name="model">mailing.trace.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Mass Mailing Statistics" disable_linking="True">
|
||||
<field name="campaign" type="row"/>
|
||||
@@ -16,9 +16,9 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_mail_statistics_report_graph" model="ir.ui.view">
|
||||
<field name="name">mail.statistics.report.graph</field>
|
||||
<field name="model">mail.statistics.report</field>
|
||||
<record id="mailing_trace_report_view_graph" model="ir.ui.view">
|
||||
<field name="name">mailing.trace.report.view.graph</field>
|
||||
<field name="model">mailing.trace.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Mass Mailing Statistics">
|
||||
<field name="campaign"/>
|
||||
@@ -27,9 +27,9 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_mail_statistics_report_search" model="ir.ui.view">
|
||||
<field name="name">mail.statistics.report.search</field>
|
||||
<field name="model">mail.statistics.report</field>
|
||||
<record id="mailing_trace_report_view_search" model="ir.ui.view">
|
||||
<field name="name">mailing.trace.report.view.search</field>
|
||||
<field name="model">mailing.trace.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Mass Mailing Statistics">
|
||||
<group expand="0" string="Extended Filters...">
|
||||
@@ -51,14 +51,14 @@
|
||||
</record>
|
||||
|
||||
<!-- Actions and Menuitems -->
|
||||
<record id="action_mail_statistics_report" model="ir.actions.act_window">
|
||||
<record id="mailing_trace_report_action" model="ir.actions.act_window">
|
||||
<field name="name">Mass Mailing Analysis</field>
|
||||
<field name="res_model">mail.statistics.report</field>
|
||||
<field name="res_model">mailing.trace.report</field>
|
||||
<field name="view_mode">graph,pivot</field>
|
||||
<field name="help">Mass Mailing Statistics allows you to check different mailing related information like number of bounced mails, opened mails, replied mails. You can sort out your analysis by different groups to get accurate grained analysis.</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="Mass Mailing" id="mass_mailing_menu_root" sequence="60"/>
|
||||
<menuitem name="Reporting" id="menu_mass_mailing_report" sequence="99"
|
||||
parent="mass_mailing_menu_root" action="action_mail_statistics_report" groups="mass_mailing.group_mass_mailing_user"/>
|
||||
parent="mass_mailing_menu_root" action="mailing_trace_report_action" groups="mass_mailing.group_mass_mailing_user"/>
|
||||
</odoo>
|
||||
@@ -1,17 +1,17 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_mass_mailing_tag,mail.mass_mailing.tag,model_mail_mass_mailing_tag,mass_mailing.group_mass_mailing_campaign,1,1,1,1
|
||||
access_mass_mailing_contact,mail.mass_mailing.contact,model_mail_mass_mailing_contact,mass_mailing.group_mass_mailing_user,1,1,1,1
|
||||
access_mass_mailing_list,mail.mass_mailing.list,model_mail_mass_mailing_list,mass_mailing.group_mass_mailing_user,1,1,1,1
|
||||
access_mass_mailing_stage,mail.mass_mailing.stage,model_mail_mass_mailing_stage,mass_mailing.group_mass_mailing_user,1,1,1,1
|
||||
access_mailing_tag,access.mailing.tag,model_mailing_tag,mass_mailing.group_mass_mailing_campaign,1,1,1,1
|
||||
access_mailing_contact_mm_user,access.mailing.contact.mm.user,model_mailing_contact,mass_mailing.group_mass_mailing_user,1,1,1,1
|
||||
access_mailing_contact_subscription_mm_user,access.mailing.contact.subscription.mm.user,model_mailing_contact_subscription,mass_mailing.group_mass_mailing_user,1,1,1,1
|
||||
access_mailing_list_mm_user,access.mailing.list.mm.user,model_mailing_list,mass_mailing.group_mass_mailing_user,1,1,1,1
|
||||
access_mailing_stage,access.mailing.stage.mm.user,model_mailing_stage,mass_mailing.group_mass_mailing_user,1,1,1,1
|
||||
access_mass_mailing_campaign,mail.mass_mailing.campaign,model_mail_mass_mailing_campaign,mass_mailing.group_mass_mailing_user,1,1,1,1
|
||||
access_mass_mailing_campaign_system,mail.mass_mailing.campaign.system,model_mail_mass_mailing_campaign,base.group_system,1,1,1,1
|
||||
access_mass_mailing,mail.mass_mailing,model_mail_mass_mailing,mass_mailing.group_mass_mailing_user,1,1,1,1
|
||||
access_mass_mailing_system,mail.mass_mailing.system,model_mail_mass_mailing,base.group_system,1,1,1,1
|
||||
access_mail_mail_statistics_user,mail.mail.statistics,model_mail_mail_statistics,base.group_user,1,1,1,1
|
||||
access_mail_mail_statistics_mass_mailing_user,mail.mail.statistics,model_mail_mail_statistics,mass_mailing.group_mass_mailing_user,1,1,1,1
|
||||
access_mail_statistics_report,mail.statistics.report,model_mail_statistics_report,mass_mailing.group_mass_mailing_user,1,1,1,1
|
||||
access_mailing_mailing_mm_user,access.mailing.mailing.mm.user,model_mailing_mailing,mass_mailing.group_mass_mailing_user,1,1,1,1
|
||||
access_mailing_mailing_system,access.mailing.mailing.system,model_mailing_mailing,base.group_system,1,1,1,1
|
||||
access_mailing_trace_user,mailing.trace.user,model_mailing_trace,base.group_user,1,1,1,1
|
||||
access_mailing_trace_mm_user,access.mailing.trace.mm.user,model_mailing_trace,mass_mailing.group_mass_mailing_user,1,1,1,1
|
||||
access_mailing_trace_report_mm_user,access.mailing.trace.report.mm.user,model_mailing_trace_report,mass_mailing.group_mass_mailing_user,1,1,1,1
|
||||
access_utm_source,access_utm_source,utm.model_utm_source,mass_mailing.group_mass_mailing_user,1,1,1,0
|
||||
access_ir_mail_server,access_ir_mail_server,base.model_ir_mail_server,mass_mailing.group_mass_mailing_user,1,0,0,0
|
||||
access_mail_blacklist_mass_mailing_user,access.mail.blacklist.mass_mailing_user,mail.model_mail_blacklist,mass_mailing.group_mass_mailing_user,1,1,1,1
|
||||
access_mail_mass_mailing_list_contact_rel,access.mail.mass_mailing_list_contact_rel,model_mail_mass_mailing_list_contact_rel,mass_mailing.group_mass_mailing_user,1,1,1,1
|
||||
access_link_tracker_mailing,access.link.tracker.mailing,link_tracker.model_link_tracker,mass_mailing.group_mass_mailing_user,1,1,1,1
|
||||
|
@@ -16,7 +16,7 @@ KanbanRecord.include({
|
||||
_openRecord: function () {
|
||||
if (this.modelName === 'mail.mass_mailing.campaign') {
|
||||
this.$('.oe_mailings').click();
|
||||
} else if (this.modelName === 'mail.mass_mailing.list' &&
|
||||
} else if (this.modelName === 'mailing.list' &&
|
||||
this.$('.o_mailing_list_kanban_boxes a')) {
|
||||
this.$('.o_mailing_list_kanban_boxes a').first().click();
|
||||
} else {
|
||||
@@ -28,7 +28,7 @@ KanbanRecord.include({
|
||||
KanbanColumn.include({
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (this.modelName === 'mail.mass_mailing') {
|
||||
if (this.modelName === 'mailing.mailing') {
|
||||
this.draggable = false;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -14,7 +14,7 @@ QUnit.module('mass_mailing', {}, function () {
|
||||
QUnit.module('field html', {
|
||||
beforeEach: function () {
|
||||
this.data = weTestUtils.wysiwygData({
|
||||
'mail.mass_mailing': {
|
||||
'mailing.mailing': {
|
||||
fields: {
|
||||
display_name: {
|
||||
string: "Displayed name",
|
||||
@@ -68,7 +68,7 @@ QUnit.test('save arch and html', async function (assert) {
|
||||
|
||||
var form = await testUtils.createView({
|
||||
View: FormView,
|
||||
model: 'mail.mass_mailing',
|
||||
model: 'mailing.mailing',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
' <field name="body_html" class="oe_read_only" widget="html"'+
|
||||
@@ -152,7 +152,7 @@ QUnit.test('save and edit arch wysiwyg', async function (assert) {
|
||||
|
||||
var form = await testUtils.createView({
|
||||
View: FormView,
|
||||
model: 'mail.mass_mailing',
|
||||
model: 'mailing.mailing',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
' <header style="min-height:31px;">' +
|
||||
|
||||
@@ -9,7 +9,7 @@ class TestMassMailingCommon(common.TransactionCase):
|
||||
def test_00_test_mass_mailing_list_merge(self):
|
||||
# Data set up
|
||||
|
||||
mailing_list_A = self.env['mail.mass_mailing.list'].create({
|
||||
mailing_list_A = self.env['mailing.list'].create({
|
||||
'name': 'A',
|
||||
'contact_ids': [
|
||||
(0, 0, {'name': 'Noel Flantier', 'email': 'noel.flantier@example.com'}),
|
||||
@@ -18,7 +18,7 @@ class TestMassMailingCommon(common.TransactionCase):
|
||||
]
|
||||
})
|
||||
|
||||
mailing_list_B = self.env['mail.mass_mailing.list'].create({
|
||||
mailing_list_B = self.env['mailing.list'].create({
|
||||
'name': 'B',
|
||||
'contact_ids': [
|
||||
(0, 0, {'name': 'Icallhimtest', 'email': 'icallhimtest@example.com'}),
|
||||
@@ -27,7 +27,7 @@ class TestMassMailingCommon(common.TransactionCase):
|
||||
]
|
||||
})
|
||||
|
||||
mailing_list_C = self.env['mail.mass_mailing.list'].create({
|
||||
mailing_list_C = self.env['mailing.list'].create({
|
||||
'name': 'C',
|
||||
'contact_ids': [
|
||||
(0, 0, {'name': 'Norberto', 'email': 'norbert@example.com'}),
|
||||
@@ -39,7 +39,7 @@ class TestMassMailingCommon(common.TransactionCase):
|
||||
# This test ensure that the mailing lists are correctly merged and no
|
||||
# duplicates are appearing in C
|
||||
|
||||
result_list = self.env['mass.mailing.list.merge'].create({
|
||||
result_list = self.env['mailing.list.merge'].create({
|
||||
'src_list_ids': [(4, list_id) for list_id in [mailing_list_A.id, mailing_list_B.id]],
|
||||
'dest_list_id': mailing_list_C.id,
|
||||
'merge_options': 'existing',
|
||||
|
||||
@@ -35,23 +35,23 @@ class TestMassMailingShortener(common.TransactionCase):
|
||||
self.addCleanup(patcher2.stop)
|
||||
|
||||
def test_00_test_mass_mailing_shortener(self):
|
||||
mailing_list_A = self.env['mail.mass_mailing.list'].create({
|
||||
mailing_list_A = self.env['mailing.list'].create({
|
||||
'name': 'A',
|
||||
})
|
||||
self.env['mail.mass_mailing.contact'].create({
|
||||
self.env['mailing.contact'].create({
|
||||
'name': 'User 1', 'email': 'user1@example.com', 'list_ids': [(4, mailing_list_A.id)]
|
||||
})
|
||||
self.env['mail.mass_mailing.contact'].create({
|
||||
self.env['mailing.contact'].create({
|
||||
'name': 'User 2', 'email': 'user2@example.com', 'list_ids': [(4, mailing_list_A.id)]
|
||||
})
|
||||
self.env['mail.mass_mailing.contact'].create({
|
||||
self.env['mailing.contact'].create({
|
||||
'name': 'User 3', 'email': 'user3@example.com', 'list_ids': [(4, mailing_list_A.id)]
|
||||
})
|
||||
|
||||
mass_mailing = self.env['mail.mass_mailing'].create({
|
||||
mass_mailing = self.env['mailing.mailing'].create({
|
||||
"reply_to_mode": "email",
|
||||
"reply_to": "Administrator <admin@yourcompany.example.com>",
|
||||
"mailing_model_id": self.env.ref('mass_mailing.model_mail_mass_mailing_list').id,
|
||||
"mailing_model_id": self.env.ref('mass_mailing.model_mailing_list').id,
|
||||
"mailing_domain": "[('list_ids', 'in', [%d])]" % mailing_list_A.id,
|
||||
"contact_list_ids": [[6, False, [mailing_list_A.id]]],
|
||||
"mass_mailing_campaign_id": False,
|
||||
|
||||
@@ -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>
|
||||
@@ -80,7 +80,7 @@
|
||||
<xpath expr="//field[@name='country_id']" position="after">
|
||||
<field name="mass_mailing_campaign_id"/>
|
||||
<field name="mass_mailing_id"/>
|
||||
<field name="mail_stat_id"/>
|
||||
<field name="mailing_trace_id"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- MAILING CONTACT SUBSCRIPTION -->
|
||||
<record model="ir.ui.view" id="mailing_contact_subscription_view_form">
|
||||
<field name="name">mailing.contact.subscription.view.form</field>
|
||||
<field name="model">mailing.contact.subscription</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="mailing_contact_subscription_view_tree">
|
||||
<field name="name">mailing.contact.subscription.view.tree</field>
|
||||
<field name="model">mailing.contact.subscription</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="mailing_contact_subscription_view_search">
|
||||
<field name="name">mailing.contact.subscription.view.search</field>
|
||||
<field name="model">mailing.contact.subscription</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', '<', 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">mailing.contact.search</field>
|
||||
<field name="model">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', '<', 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">mailing.contact.tree</field>
|
||||
<field name="model">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">mailing.contact.kanban</field>
|
||||
<field name="model">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">mailing.contact.form</field>
|
||||
<field name="model">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">mailing.contact.pivot</field>
|
||||
<field name="model">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">mailing.contact.graph</field>
|
||||
<field name="model">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">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">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="mailing_list_view_search">
|
||||
<field name="name">mailing.list.view.search</field>
|
||||
<field name="model">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="mailing_list_view_tree">
|
||||
<field name="name">mailing.list.view.tree</field>
|
||||
<field name="model">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="mailing_list_view_form">
|
||||
<field name="name">mailing.list.form</field>
|
||||
<field name="model">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="mailing_list_view_form_simplified" model="ir.ui.view">
|
||||
<field name="name">mailing.list.form.simplified</field>
|
||||
<field name="model">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">mailing.list</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="mailing_list_view_form_simplified"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<record id="mailing_list_view_kanban" model="ir.ui.view">
|
||||
<field name="name">mailing.list.view.kanban</field>
|
||||
<field name="model">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">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>
|
||||
+25
-455
@@ -1,439 +1,9 @@
|
||||
<?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', '<', 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', '<', 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>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<field name="name">mailing.mailing.search</field>
|
||||
<field name="model">mailing.mailing</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Mailings">
|
||||
<field name="name" string="Mailings"/>
|
||||
@@ -456,8 +26,8 @@
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_tree">
|
||||
<field name="name">mail.mass_mailing.tree</field>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<field name="name">mailing.mailing.tree</field>
|
||||
<field name="model">mailing.mailing</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mailings">
|
||||
@@ -473,8 +43,8 @@
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_form">
|
||||
<field name="name">mail.mass_mailing.form</field>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<field name="name">mailing.mailing.form</field>
|
||||
<field name="model">mailing.mailing</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Mailing">
|
||||
<header style="min-height:31px;">
|
||||
@@ -588,10 +158,10 @@
|
||||
<field name="mailing_model_name" invisible="1"/>
|
||||
<field name="mailing_model_real" invisible="1"/>
|
||||
<field name="mailing_domain" widget="domain"
|
||||
attrs="{'invisible': [('mailing_model_name', '=', 'mail.mass_mailing.list')]}"
|
||||
attrs="{'invisible': [('mailing_model_name', '=', 'mailing.list')]}"
|
||||
options="{'model': 'mailing_model_real'}">
|
||||
</field>
|
||||
<div attrs="{'invisible': [('mailing_model_name', '!=', 'mail.mass_mailing.list')]}">
|
||||
<div attrs="{'invisible': [('mailing_model_name', '!=', 'mailing.list')]}">
|
||||
<label for="contact_list_ids" string="Select mailing lists:" class="oe_edit_only"/>
|
||||
<field name="contact_list_ids" widget="many2many_tags"
|
||||
placeholder="Select mailing lists..." class="oe_inline"/>
|
||||
@@ -617,7 +187,7 @@
|
||||
<label for="reply_to"/>
|
||||
<div>
|
||||
<field name="reply_to_mode" widget="radio"
|
||||
attrs="{'invisible': [('mailing_model_name', 'in', ['mail.mass_mailing.contact', 'res.partner', 'mail.mass_mailing.list'])]}"/>
|
||||
attrs="{'invisible': [('mailing_model_name', 'in', ['mailing.contact', 'res.partner', 'mailing.list'])]}"/>
|
||||
<field name="reply_to"
|
||||
attrs="{'required': [('reply_to_mode', '=', 'email')], 'invisible': [('reply_to_mode', '=', 'thread')]}"/>
|
||||
</div>
|
||||
@@ -660,8 +230,8 @@
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_kanban">
|
||||
<field name="name">mail.mass_mailing.kanban</field>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<field name="name">mailing.mailing.kanban</field>
|
||||
<field name="model">mailing.mailing</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban default_group_by="state">
|
||||
<field name='state' readonly="1"/>
|
||||
@@ -745,8 +315,8 @@
|
||||
</record>
|
||||
|
||||
<record id="view_mail_mass_mailing_graph" model="ir.ui.view">
|
||||
<field name="name">mail.mass_mailing.graph</field>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<field name="name">mailing.mailing.graph</field>
|
||||
<field name="model">mailing.mailing</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Mailing" type="bar">
|
||||
<field name="state" type="row"/>
|
||||
@@ -756,7 +326,7 @@
|
||||
|
||||
<record id="action_view_mass_mailings" model="ir.actions.act_window">
|
||||
<field name="name">Mailings</field>
|
||||
<field name="res_model">mail.mass_mailing</field>
|
||||
<field name="res_model">mailing.mailing</field>
|
||||
<field name="view_mode">kanban,tree,form,graph</field>
|
||||
<field name="context">{
|
||||
'search_default_assigned_to_me': 1,
|
||||
@@ -774,7 +344,7 @@
|
||||
|
||||
<record id="action_view_mass_mailings_from_campaign" model="ir.actions.act_window">
|
||||
<field name="name">Mailings</field>
|
||||
<field name="res_model">mail.mass_mailing</field>
|
||||
<field name="res_model">mailing.mailing</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="context">{
|
||||
'search_default_assigned_to_me': 1,
|
||||
@@ -795,7 +365,7 @@
|
||||
|
||||
<record id="action_create_mass_mailings_from_campaign" model="ir.actions.act_window">
|
||||
<field name="name">Mailings</field>
|
||||
<field name="res_model">mail.mass_mailing</field>
|
||||
<field name="res_model">mailing.mailing</field>
|
||||
<field name="view_mode">form,kanban,tree</field>
|
||||
<field name="context">{
|
||||
'search_default_assigned_to_me': 1,
|
||||
@@ -816,7 +386,7 @@
|
||||
|
||||
<record id="action_create_ab_mass_mailings_from_campaign" model="ir.actions.act_window">
|
||||
<field name="name">Mailings</field>
|
||||
<field name="res_model">mail.mass_mailing</field>
|
||||
<field name="res_model">mailing.mailing</field>
|
||||
<field name="view_mode">form,kanban,tree</field>
|
||||
<field name="context">{
|
||||
'search_default_assigned_to_me': 1,
|
||||
@@ -841,8 +411,8 @@
|
||||
|
||||
<!-- MAILING CAMPAIGN STAGE !-->
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_stage_search">
|
||||
<field name="name">mail.mass_mailing.stage.search</field>
|
||||
<field name="model">mail.mass_mailing.stage</field>
|
||||
<field name="name">mailing.stage.search</field>
|
||||
<field name="model">mailing.stage</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Mailings">
|
||||
<field name="name"/>
|
||||
@@ -851,8 +421,8 @@
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_stage_tree">
|
||||
<field name="name">mail.mass_mailing.stage.tree</field>
|
||||
<field name="model">mail.mass_mailing.stage</field>
|
||||
<field name="name">mailing.stage.tree</field>
|
||||
<field name="model">mailing.stage</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mailings" editable="top">
|
||||
@@ -863,7 +433,7 @@
|
||||
</record>
|
||||
<record id="action_view_mass_mailing_stages" model="ir.actions.act_window">
|
||||
<field name="name">Mailing Stages</field>
|
||||
<field name="res_model">mail.mass_mailing.stage</field>
|
||||
<field name="res_model">mailing.stage</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
@@ -879,8 +449,8 @@
|
||||
|
||||
<!-- MAILING CAMPAIGN TAGS !-->
|
||||
<record id="mail_mass_mailing_tag_view_form" model="ir.ui.view">
|
||||
<field name="name">mail.mass_mailing.tag.form</field>
|
||||
<field name="model">mail.mass_mailing.tag</field>
|
||||
<field name="name">mailing.tag.form</field>
|
||||
<field name="model">mailing.tag</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Campaign Tags">
|
||||
<sheet>
|
||||
@@ -893,7 +463,7 @@
|
||||
</record>
|
||||
<record id="mass_mailing_tag_action" model="ir.actions.act_window">
|
||||
<field name="name">Campaign Tags</field>
|
||||
<field name="res_model">mail.mass_mailing.tag</field>
|
||||
<field name="res_model">mailing.tag</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Add a new tag
|
||||
+6
@@ -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>
|
||||
+14
-14
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- MAIL MAIL STATISTICS !-->
|
||||
<record model="ir.ui.view" id="view_mail_mail_statistics_search">
|
||||
<field name="name">mail.mail.statistics.search</field>
|
||||
<field name="model">mail.mail.statistics</field>
|
||||
<!-- MAILING TRACE !-->
|
||||
<record model="ir.ui.view" id="mailing_trace_view_search">
|
||||
<field name="name">mailing.trace.search</field>
|
||||
<field name="model">mailing.trace</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Mail Statistics">
|
||||
<field name="mail_mail_id_int"/>
|
||||
@@ -30,9 +30,9 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mail_statistics_tree">
|
||||
<field name="name">mail.mail.statistics.tree</field>
|
||||
<field name="model">mail.mail.statistics</field>
|
||||
<record model="ir.ui.view" id="mailing_trace_view_tree">
|
||||
<field name="name">mailing.trace.tree</field>
|
||||
<field name="model">mailing.trace</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mail Statistics" create="0">
|
||||
<field name="email"/>
|
||||
@@ -49,9 +49,9 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mail_statistics_form">
|
||||
<field name="name">mail.mail.statistics.form</field>
|
||||
<field name="model">mail.mail.statistics</field>
|
||||
<record model="ir.ui.view" id="mailing_trace_view_form">
|
||||
<field name="name">mailing.trace.form</field>
|
||||
<field name="model">mailing.trace</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Mail Statistics" create="0">
|
||||
<header>
|
||||
@@ -88,13 +88,13 @@
|
||||
|
||||
<record id="action_view_mail_mail_statistics" model="ir.actions.act_window">
|
||||
<field name="name">Mail Statistics</field>
|
||||
<field name="res_model">mail.mail.statistics</field>
|
||||
<field name="res_model">mailing.trace</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_mail_mail_statistics_mailing" model="ir.actions.act_window">
|
||||
<field name="name">Mail Statistics</field>
|
||||
<field name="res_model">mail.mail.statistics</field>
|
||||
<field name="res_model">mailing.trace</field>
|
||||
<field name="view_mode">graph,tree,form,pivot</field>
|
||||
<field name="domain">[]</field>
|
||||
<field name="context">{'search_default_mass_mailing_id': active_id}</field>
|
||||
@@ -102,7 +102,7 @@
|
||||
|
||||
<record id="action_view_mail_mail_statistics_mailing_list" model="ir.actions.act_window">
|
||||
<field name="name">Emails Sent</field>
|
||||
<field name="res_model">mail.mail.statistics</field>
|
||||
<field name="res_model">mailing.trace</field>
|
||||
<field name="view_mode">tree,graph,form,pivot</field>
|
||||
<field name="domain">[]</field>
|
||||
<field name="context">{'search_default_mass_mailing_id': active_id}</field>
|
||||
@@ -110,7 +110,7 @@
|
||||
|
||||
<record id="view_mail_mail_statistics_graph" model="ir.ui.view">
|
||||
<field name="name">Mail Statistics Graph</field>
|
||||
<field name="model">mail.mail.statistics</field>
|
||||
<field name="model">mailing.trace</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Mail Statistics" type="bar" stacked="True" interval="day">
|
||||
<field name="state_update" type="row" interval="day"/>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_mailing
|
||||
from . import mail_compose_message
|
||||
from . import mass_mailing_list_merge
|
||||
from . import mass_mailing_schedule_date
|
||||
from . import mailing_list_merge
|
||||
from . import mailing_mailing_schedule_date
|
||||
from . import mailing_mailing_test
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
# -*- 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):
|
||||
"""Add concept of mass mailing campaign to the mail.compose.message wizard
|
||||
"""
|
||||
_inherit = 'mail.compose.message'
|
||||
|
||||
mass_mailing_campaign_id = fields.Many2one('mail.mass_mailing.campaign', string='Mass Mailing Campaign')
|
||||
mass_mailing_id = fields.Many2one('mail.mass_mailing', string='Mass Mailing', ondelete='cascade')
|
||||
mass_mailing_id = fields.Many2one('mailing.mailing', string='Mass Mailing', ondelete='cascade')
|
||||
mass_mailing_name = fields.Char(string='Mass Mailing Name')
|
||||
mailing_list_ids = fields.Many2many('mail.mass_mailing.list', string='Mailing List')
|
||||
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
|
||||
mail.mail.statistics values in the o2m of mail_mail, when doing pure
|
||||
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)
|
||||
@@ -28,7 +27,7 @@ class MailComposeMessage(models.TransientModel):
|
||||
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['mail.mass_mailing'].create({
|
||||
mass_mailing = self.env['mailing.mailing'].create({
|
||||
'mass_mailing_campaign_id': self.mass_mailing_campaign_id.id,
|
||||
'name': self.mass_mailing_name,
|
||||
'template_id': self.template_id.id,
|
||||
@@ -71,7 +70,7 @@ class MailComposeMessage(models.TransientModel):
|
||||
mail_values['state'] = 'cancel'
|
||||
elif seen_list is not None:
|
||||
seen_list.add(mail_to)
|
||||
stat_vals = {
|
||||
trace_vals = {
|
||||
'model': self.model,
|
||||
'res_id': res_id,
|
||||
'mass_mailing_id': mass_mailing.id,
|
||||
@@ -79,12 +78,12 @@ class MailComposeMessage(models.TransientModel):
|
||||
}
|
||||
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 stat when still-born
|
||||
# propagate ignored state to trace when still-born
|
||||
if mail_values.get('state') == 'cancel':
|
||||
stat_vals['ignored'] = fields.Datetime.now()
|
||||
trace_vals['ignored'] = fields.Datetime.now()
|
||||
mail_values.update({
|
||||
'mailing_id': mass_mailing.id,
|
||||
'statistics_ids': [(0, 0, stat_vals)],
|
||||
'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,
|
||||
|
||||
+4
-4
@@ -5,11 +5,11 @@ from odoo import api, fields, models
|
||||
|
||||
|
||||
class MassMailingListMerge(models.TransientModel):
|
||||
_name = 'mass.mailing.list.merge'
|
||||
_name = 'mailing.list.merge'
|
||||
_description = 'Merge Mass Mailing List'
|
||||
|
||||
src_list_ids = fields.Many2many('mail.mass_mailing.list', string='Mailing Lists')
|
||||
dest_list_id = fields.Many2one('mail.mass_mailing.list', string='Destination Mailing List')
|
||||
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'),
|
||||
@@ -29,7 +29,7 @@ class MassMailingListMerge(models.TransientModel):
|
||||
|
||||
def action_mailing_lists_merge(self):
|
||||
if self.merge_options == 'new':
|
||||
self.dest_list_id = self.env['mail.mass_mailing.list'].create({
|
||||
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)
|
||||
+6
-6
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<!-- Merge Mailing List -->
|
||||
<record id="mass_mailing_list_merge_view_form" model="ir.ui.view">
|
||||
<field name="name">mass.mailing.list.merge.form</field>
|
||||
<field name="model">mass.mailing.list.merge</field>
|
||||
<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>
|
||||
@@ -26,8 +26,8 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<act_window id="mass_mailing_list_merge_action" name="Merge Selected Mailing Lists"
|
||||
res_model="mass.mailing.list.merge"
|
||||
binding_model="mail.mass_mailing.list" binding_views="list"
|
||||
<act_window id="mailing_list_merge_action" name="Merge Selected Mailing Lists"
|
||||
res_model="mailing.list.merge"
|
||||
binding_model="mailing.list" binding_views="list"
|
||||
view_mode="form" target="new"/>
|
||||
</odoo>
|
||||
+3
-3
@@ -5,12 +5,12 @@ from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class MassMailingScheduleDate(models.TransientModel):
|
||||
_name = 'mass.mailing.schedule.date'
|
||||
class MailingMailingScheduleDate(models.TransientModel):
|
||||
_name = 'mailing.mailing.schedule.date'
|
||||
_description = 'Mass Mailing Scheduling'
|
||||
|
||||
schedule_date = fields.Datetime(string='Schedule in the Future')
|
||||
mass_mailing_id = fields.Many2one('mail.mass_mailing', required=True)
|
||||
mass_mailing_id = fields.Many2one('mailing.mailing', required=True)
|
||||
|
||||
@api.constrains('schedule_date')
|
||||
def _check_schedule_date(self):
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<record id="mass_mailing_schedule_date_view_form" model="ir.ui.view">
|
||||
<field name="name">mass.mailing.schedule.date.view.form</field>
|
||||
<field name="model">mass.mailing.schedule.date</field>
|
||||
<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>
|
||||
@@ -18,9 +18,9 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mass_mailing_schedule_date_action" model="ir.actions.act_window">
|
||||
<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">mass.mailing.schedule.date</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>
|
||||
+2
-2
@@ -5,12 +5,12 @@ from odoo import api, fields, models, tools
|
||||
|
||||
|
||||
class TestMassMailing(models.TransientModel):
|
||||
_name = 'mail.mass_mailing.test'
|
||||
_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['mail.message']._get_default_from())
|
||||
mass_mailing_id = fields.Many2one('mail.mass_mailing', string='Mailing', required=True, ondelete='cascade')
|
||||
mass_mailing_id = fields.Many2one('mailing.mailing', string='Mailing', required=True, ondelete='cascade')
|
||||
|
||||
def send_mail_test(self):
|
||||
self.ensure_one()
|
||||
+3
-3
@@ -2,8 +2,8 @@
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_test_form">
|
||||
<field name="name">mail.mass_mailing.test.form</field>
|
||||
<field name="model">mail.mass_mailing.test</field>
|
||||
<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">
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<record id="action_mail_mass_mailing_test" model="ir.actions.act_window">
|
||||
<field name="name">Mailing Test</field>
|
||||
<field name="res_model">mail.mass_mailing.test</field>
|
||||
<field name="res_model">mailing.mailing.test</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
@@ -9,7 +9,7 @@
|
||||
'description': """UTM and mass mailing on lead / opportunities""",
|
||||
'depends': ['crm', 'mass_mailing'],
|
||||
'data': [
|
||||
'views/mass_mailing_views.xml',
|
||||
'views/mailing_mailing_views.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/mass_mailing_demo.xml',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="mass_mail_lead_0" model="mail.mass_mailing">
|
||||
<record id="mass_mail_lead_0" model="mailing.mailing">
|
||||
<field name="name">Lead Recall</field>
|
||||
<field name="subject">We want to hear form you !</field>
|
||||
<field name="state">in_queue</field>
|
||||
@@ -10,7 +10,6 @@
|
||||
<field name="mass_mailing_campaign_id" eval="ref('mass_mailing.mass_mail_campaign_1')"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="source_id" ref="utm.utm_source_mailing"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
<field name="mailing_model_id" ref="crm.model_crm_lead"/>
|
||||
<field name="mailing_domain">[]</field>
|
||||
<field name="reply_to_mode">email</field>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import mass_mailing
|
||||
from . import mailing_mailing
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@ from odoo.osv import expression
|
||||
|
||||
|
||||
class MassMailing(models.Model):
|
||||
_name = 'mail.mass_mailing'
|
||||
_inherit = 'mail.mass_mailing'
|
||||
_name = 'mailing.mailing'
|
||||
_inherit = 'mailing.mailing'
|
||||
|
||||
crm_lead_activated = fields.Boolean('Use Leads', compute='_compute_crm_lead_activated')
|
||||
crm_lead_count = fields.Integer('Lead Count', groups='sales_team.group_sale_salesman', compute='_compute_crm_lead_and_opportunities_count')
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="mail_mass_mailing_view_form" model="ir.ui.view">
|
||||
<field name="name">mail.mass_mailing.view.form.inherit.crm</field>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<record id="mailing_mailing_view_form" model="ir.ui.view">
|
||||
<field name="name">mailing.mailing.view.form.inherit.crm</field>
|
||||
<field name="model">mailing.mailing</field>
|
||||
<field name="inherit_id" ref="mass_mailing.view_mail_mass_mailing_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[hasclass('o_mass_mailing_button')]" position="before">
|
||||
@@ -2,4 +2,4 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import event
|
||||
from . import mass_mailing
|
||||
from . import mailing_mailing
|
||||
|
||||
@@ -15,7 +15,7 @@ class Event(models.Model):
|
||||
mass_mailing_action = dict(
|
||||
name='Mass Mail Attendees',
|
||||
type='ir.actions.act_window',
|
||||
res_model='mail.mass_mailing',
|
||||
res_model='mailing.mailing',
|
||||
view_mode='form',
|
||||
target='current',
|
||||
context=dict(
|
||||
|
||||
+2
-1
@@ -2,10 +2,11 @@ from odoo import models, api
|
||||
|
||||
|
||||
class MassMailingCampaign(models.Model):
|
||||
_inherit = "mail.mass_mailing"
|
||||
_inherit = "mailing.mailing"
|
||||
|
||||
@api.onchange('mailing_model_id', 'contact_list_ids')
|
||||
def _onchange_model_and_list(self):
|
||||
# TDE FIXME: whuuut ?
|
||||
result = super(MassMailingCampaign, self)._onchange_model_and_list()
|
||||
if self.mailing_model_name == 'event.registration' and self.mailing_domain == '[]':
|
||||
self.mailing_domain = self.env.context.get('default_mailing_domain', '[]')
|
||||
@@ -11,7 +11,7 @@ class Event(models.Model):
|
||||
mass_mailing_action = dict(
|
||||
name='Mass Mail Attendees',
|
||||
type='ir.actions.act_window',
|
||||
res_model='mail.mass_mailing',
|
||||
res_model='mailing.mailing',
|
||||
view_mode='form',
|
||||
target='current',
|
||||
context=dict(
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
'description': """UTM and mass mailing on sale orders""",
|
||||
'depends': ['sale', 'mass_mailing'],
|
||||
'data': [
|
||||
'views/mass_mailing_views.xml',
|
||||
'views/mailing_mailing_views.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/mass_mailing_demo.xml',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<data noupdate="0">
|
||||
<record id="mass_mail_sale_order_0" model="mail.mass_mailing">
|
||||
<record id="mass_mail_sale_order_0" model="mailing.mailing">
|
||||
<field name="name">Sale Promotion 1</field>
|
||||
<field name="subject">Our last promotions, just for you !</field>
|
||||
<field name="state">in_queue</field>
|
||||
@@ -10,7 +10,6 @@
|
||||
<field name="mass_mailing_campaign_id" eval="ref('mass_mailing.mass_mail_campaign_1')"/>
|
||||
<field name="campaign_id" ref="utm.utm_campaign_email_campaign_products"/>
|
||||
<field name="source_id" ref="sale.utm_source_sale_order_0"/>
|
||||
<field name="medium_id" ref="utm.utm_medium_email"/>
|
||||
<field name="mailing_model_id" ref="sale.model_sale_order"/>
|
||||
<field name="mailing_domain">[]</field>
|
||||
<field name="reply_to_mode">email</field>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import mass_mailing
|
||||
from . import mailing_mailing
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ from odoo import api, fields, models
|
||||
|
||||
|
||||
class MassMailing(models.Model):
|
||||
_name = 'mail.mass_mailing'
|
||||
_inherit = 'mail.mass_mailing'
|
||||
_name = 'mailing.mailing'
|
||||
_inherit = 'mailing.mailing'
|
||||
|
||||
sale_quotation_count = fields.Integer('Quotation Count', groups='sales_team.group_sale_salesman', compute='_compute_sale_quotation_count')
|
||||
sale_invoiced_amount = fields.Integer('Invoiced Amount', groups='sales_team.group_sale_salesman', compute='_compute_sale_invoiced_amount')
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="mail_mass_mailing_view_form" model="ir.ui.view">
|
||||
<field name="name">mail.mass_mailing.view.form.inherit.sale</field>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<record id="mailing_mailing_view_form" model="ir.ui.view">
|
||||
<field name="name">mailing.mailing.view.form.inherit.sale</field>
|
||||
<field name="model">mailing.mailing</field>
|
||||
<field name="inherit_id" ref="mass_mailing.view_mail_mass_mailing_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[hasclass('o_mass_mailing_button')]" position="before">
|
||||
@@ -15,7 +15,7 @@ class TestComposer(common.SavepointCase):
|
||||
)
|
||||
|
||||
test_record = self.env['res.partner'].create({'name': 'Mass Mail Partner'})
|
||||
mass_mail_record = self.env['mail.mass_mailing'].with_user(test_user).create({
|
||||
mass_mail_record = self.env['mailing.mailing'].with_user(test_user).create({
|
||||
'name': 'Test',
|
||||
'subject': 'Test',
|
||||
'state': 'draft',
|
||||
|
||||
@@ -45,10 +45,10 @@ class TestLinkTracker(common.MassMailingCase):
|
||||
|
||||
@users('marketing')
|
||||
def test_add_link_mail_stat(self):
|
||||
mailing = self.env['mail.mass_mailing'].create({'name': 'Test Mailing', "subject": "Hi!"})
|
||||
mailing = self.env['mailing.mailing'].create({'name': 'Test Mailing', "subject": "Hi!"})
|
||||
code = self.link.code
|
||||
self.assertEqual(self.link.count, 1)
|
||||
stat = self.env['mail.mail.statistics'].create({'mass_mailing_id': mailing.id})
|
||||
stat = self.env['mailing.trace'].create({'mass_mailing_id': mailing.id})
|
||||
self.assertFalse(stat.opened)
|
||||
self.assertFalse(stat.clicked)
|
||||
|
||||
@@ -57,7 +57,7 @@ class TestLinkTracker(common.MassMailingCase):
|
||||
code,
|
||||
ip='100.00.00.01',
|
||||
country_code='BEL',
|
||||
mail_stat_id=stat.id
|
||||
mailing_trace_id=stat.id
|
||||
)
|
||||
self.assertEqual(self.link.count, 2)
|
||||
self.assertEqual(click.mass_mailing_id, mailing)
|
||||
|
||||
@@ -6,10 +6,10 @@ import datetime
|
||||
class TestAutoBlacklist(common.TransactionCase):
|
||||
|
||||
def test_mail_bounced_auto_blacklist(self):
|
||||
mass_mailing_contacts = self.env['mail.mass_mailing.contact']
|
||||
mass_mailing = self.env['mail.mass_mailing']
|
||||
mass_mailing_contacts = self.env['mailing.contact']
|
||||
mass_mailing = self.env['mailing.mailing']
|
||||
mail_blacklist = self.env['mail.blacklist']
|
||||
mail_statistics = self.env['mail.mail.statistics']
|
||||
mail_statistics = self.env['mailing.trace']
|
||||
mail_thread = self.env['mail.thread']
|
||||
|
||||
# create mailing contact record
|
||||
@@ -17,35 +17,34 @@ class TestAutoBlacklist(common.TransactionCase):
|
||||
|
||||
# create bounced history
|
||||
mail_statistics.create({
|
||||
'model': 'mail.mass_mailing.contact',
|
||||
'model': 'mailing.contact',
|
||||
'res_id': self.mailing_contact_1.id,
|
||||
'bounced': datetime.datetime.now() - datetime.timedelta(weeks=2),
|
||||
'email': self.mailing_contact_1.email
|
||||
})
|
||||
self.mailing_contact_1._message_receive_bounce(self.mailing_contact_1.email, self.mailing_contact_1)
|
||||
mail_statistics.create({
|
||||
'model': 'mail.mass_mailing.contact',
|
||||
'model': 'mailing.contact',
|
||||
'res_id': self.mailing_contact_1.id,
|
||||
'bounced': datetime.datetime.now() - datetime.timedelta(weeks=3),
|
||||
'email': self.mailing_contact_1.email
|
||||
})
|
||||
self.mailing_contact_1._message_receive_bounce(self.mailing_contact_1.email, self.mailing_contact_1)
|
||||
mail_statistics.create({
|
||||
'model': 'mail.mass_mailing.contact',
|
||||
'model': 'mailing.contact',
|
||||
'res_id': self.mailing_contact_1.id,
|
||||
'bounced': datetime.datetime.now() - datetime.timedelta(weeks=4),
|
||||
'email': self.mailing_contact_1.email
|
||||
})
|
||||
self.mailing_contact_1._message_receive_bounce(self.mailing_contact_1.email, self.mailing_contact_1)
|
||||
mail_statistics.create({
|
||||
'model': 'mail.mass_mailing.contact',
|
||||
'model': 'mailing.contact',
|
||||
'res_id': self.mailing_contact_1.id,
|
||||
'bounced': datetime.datetime.now() - datetime.timedelta(weeks=5),
|
||||
'email': self.mailing_contact_1.email
|
||||
})
|
||||
self.mailing_contact_1._message_receive_bounce(self.mailing_contact_1.email, self.mailing_contact_1)
|
||||
|
||||
|
||||
# create mass mailing record
|
||||
self.mass_mailing = mass_mailing.create({
|
||||
'name': 'test',
|
||||
@@ -54,7 +53,7 @@ class TestAutoBlacklist(common.TransactionCase):
|
||||
[self.mailing_contact_1.id])],
|
||||
'body_html': 'This is a bounced mail for auto blacklist demo'})
|
||||
self.mass_mailing.put_in_queue()
|
||||
res_ids = self.mass_mailing.get_remaining_recipients()
|
||||
res_ids = self.mass_mailing._get_remaining_recipients()
|
||||
composer_values = {
|
||||
'body': self.mass_mailing.convert_links()[self.mass_mailing.id],
|
||||
'subject': self.mass_mailing.name,
|
||||
@@ -71,7 +70,7 @@ class TestAutoBlacklist(common.TransactionCase):
|
||||
composer.send_mail()
|
||||
|
||||
mail_statistics.create({
|
||||
'model': 'mail.mass_mailing.contact',
|
||||
'model': 'mailing.contact',
|
||||
'res_id': self.mailing_contact_1.id,
|
||||
'bounced': datetime.datetime.now(),
|
||||
'email': self.mailing_contact_1.email
|
||||
|
||||
@@ -32,7 +32,7 @@ class TestMassMail(MassMailingCase):
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink')
|
||||
def test_link_tracker(self):
|
||||
_url = 'https://www.example.com/foo/bar?baz=qux'
|
||||
mailing = self.env['mail.mass_mailing'].create({
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'TestMailing',
|
||||
'subject': 'Test',
|
||||
'medium_id': self.test_medium.id,
|
||||
@@ -42,7 +42,7 @@ class TestMassMail(MassMailingCase):
|
||||
'mailing_domain': [('id', 'in', self.mm_recs.ids)],
|
||||
})
|
||||
|
||||
mailing.send_mail()
|
||||
mailing.action_send_mail()
|
||||
|
||||
# basic test emails are sent
|
||||
self.assertEqual(mailing.sent, 5)
|
||||
@@ -68,8 +68,8 @@ class TestMassMail(MassMailingCase):
|
||||
class TestAccessRights(TransactionCase):
|
||||
|
||||
def test_01_mass_mail_blacklist(self):
|
||||
mass_mailing_contacts = self.env['mail.mass_mailing.contact']
|
||||
mass_mailing = self.env['mail.mass_mailing']
|
||||
mass_mailing_contacts = self.env['mailing.contact']
|
||||
mass_mailing = self.env['mailing.mailing']
|
||||
mail_blacklist = self.env['mail.blacklist']
|
||||
|
||||
# create mailing contact record
|
||||
@@ -92,7 +92,7 @@ class TestAccessRights(TransactionCase):
|
||||
self.mailing_contact_4.id, self.mailing_contact_5.id])],
|
||||
'body_html': 'This is mass mail marketing demo'})
|
||||
self.mass_mailing.put_in_queue()
|
||||
res_ids = self.mass_mailing.get_remaining_recipients()
|
||||
res_ids = self.mass_mailing._get_remaining_recipients()
|
||||
composer_values = {
|
||||
'body': self.mass_mailing.convert_links()[self.mass_mailing.id],
|
||||
'subject': self.mass_mailing.name,
|
||||
@@ -110,10 +110,10 @@ class TestAccessRights(TransactionCase):
|
||||
'blacklist ignored email number incorrect, should be equals to 2')
|
||||
|
||||
def test_02_mass_mail_simple_opt_out(self):
|
||||
mass_mailing_contacts = self.env['mail.mass_mailing.contact']
|
||||
mass_mailing_lists = self.env['mail.mass_mailing.list']
|
||||
mass_mailing_optout = self.env['mail.mass_mailing.list_contact_rel']
|
||||
mass_mailing = self.env['mail.mass_mailing']
|
||||
mass_mailing_contacts = self.env['mailing.contact']
|
||||
mass_mailing_lists = self.env['mailing.list']
|
||||
mass_mailing_optout = self.env['mailing.contact.subscription']
|
||||
mass_mailing = self.env['mailing.mailing']
|
||||
|
||||
# create mailing contact record
|
||||
mailing_contact_1 = mass_mailing_contacts.create({'name': 'test email 1', 'email': 'test1@email.com'})
|
||||
@@ -159,10 +159,10 @@ class TestAccessRights(TransactionCase):
|
||||
'Opt Out ignored email number incorrect, should be equals to 1')
|
||||
|
||||
def test_03_mass_mail_multi_opt_out(self):
|
||||
mass_mailing_contacts = self.env['mail.mass_mailing.contact']
|
||||
mass_mailing_lists = self.env['mail.mass_mailing.list']
|
||||
mass_mailing_optout = self.env['mail.mass_mailing.list_contact_rel']
|
||||
mass_mailing = self.env['mail.mass_mailing']
|
||||
mass_mailing_contacts = self.env['mailing.contact']
|
||||
mass_mailing_lists = self.env['mailing.list']
|
||||
mass_mailing_optout = self.env['mailing.contact.subscription']
|
||||
mass_mailing = self.env['mailing.mailing']
|
||||
|
||||
# create mailing contact record
|
||||
mailing_contact_1 = mass_mailing_contacts.create({'name': 'test email 1', 'email': 'test1@email.com'})
|
||||
@@ -216,10 +216,10 @@ class TestAccessRights(TransactionCase):
|
||||
'Opt Out ignored email number incorrect, should be equals to 0')
|
||||
|
||||
def test_04_mass_mail_multi_users_different_opt_out(self):
|
||||
mass_mailing_contacts = self.env['mail.mass_mailing.contact']
|
||||
mass_mailing_lists = self.env['mail.mass_mailing.list']
|
||||
mass_mailing_optout = self.env['mail.mass_mailing.list_contact_rel']
|
||||
mass_mailing = self.env['mail.mass_mailing']
|
||||
mass_mailing_contacts = self.env['mailing.contact']
|
||||
mass_mailing_lists = self.env['mailing.list']
|
||||
mass_mailing_optout = self.env['mailing.contact.subscription']
|
||||
mass_mailing = self.env['mailing.mailing']
|
||||
|
||||
# create mailing contact record
|
||||
mailing_contact_1 = mass_mailing_contacts.create({'name': 'test email (A)', 'email': 'test@email.com'})
|
||||
@@ -271,7 +271,7 @@ class TestOnResPartner(TransactionCase):
|
||||
|
||||
def test_mass_mail_on_res_partner(self):
|
||||
partners = self.env['res.partner']
|
||||
mass_mailing = self.env['mail.mass_mailing']
|
||||
mass_mailing = self.env['mailing.mailing']
|
||||
|
||||
# create mailing contact record
|
||||
partner_a = partners.create({
|
||||
@@ -296,7 +296,7 @@ class TestOnResPartner(TransactionCase):
|
||||
'body_html': 'This is mass mail marketing demo'})
|
||||
self.mass_mailing.mailing_model_real = 'res.partner'
|
||||
self.mass_mailing.put_in_queue()
|
||||
res_ids = self.mass_mailing.get_remaining_recipients()
|
||||
res_ids = self.mass_mailing._get_remaining_recipients()
|
||||
composer_values = {
|
||||
'body': self.mass_mailing.convert_links()[self.mass_mailing.id],
|
||||
'subject': self.mass_mailing.name,
|
||||
|
||||
@@ -41,7 +41,7 @@ class TestMassMailPerformance(TestMassMailPerformanceBase):
|
||||
@warmup
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests')
|
||||
def test_send_mailing(self):
|
||||
mailing = self.env['mail.mass_mailing'].create({
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'Test',
|
||||
'subject': 'Test',
|
||||
'body_html': '<p>Hello <a role="button" href="https://www.example.com/foo/bar?baz=qux">quux</a><a role="button" href="/unsubscribe_from_list">Unsubscribe</a></p>',
|
||||
@@ -51,7 +51,7 @@ class TestMassMailPerformance(TestMassMailPerformanceBase):
|
||||
})
|
||||
|
||||
with self.assertQueryCount(__system__=2477, marketing=3128):
|
||||
mailing.send_mail()
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertEqual(mailing.sent, 50)
|
||||
self.assertEqual(mailing.delivered, 50)
|
||||
@@ -79,7 +79,7 @@ class TestMassMailBlPerformance(TestMassMailPerformanceBase):
|
||||
@warmup
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests')
|
||||
def test_send_mailing_w_bl(self):
|
||||
mailing = self.env['mail.mass_mailing'].create({
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'Test',
|
||||
'subject': 'Test',
|
||||
'body_html': '<p>Hello <a role="button" href="https://www.example.com/foo/bar?baz=qux">quux</a><a role="button" href="/unsubscribe_from_list">Unsubscribe</a></p>',
|
||||
@@ -89,7 +89,7 @@ class TestMassMailBlPerformance(TestMassMailPerformanceBase):
|
||||
})
|
||||
|
||||
with self.assertQueryCount(__system__=2864, marketing=3611):
|
||||
mailing.send_mail()
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertEqual(mailing.sent, 50)
|
||||
self.assertEqual(mailing.delivered, 50)
|
||||
|
||||
@@ -15,7 +15,8 @@ On a simple click, your visitors can subscribe to mailing lists managed in the E
|
||||
'security/ir.model.access.csv',
|
||||
'views/website_mass_mailing_templates.xml',
|
||||
'views/snippets_templates.xml',
|
||||
'views/mass_mailing_view.xml',
|
||||
'views/mailing_list_views.xml',
|
||||
'views/website_mass_mailing_views.xml',
|
||||
],
|
||||
'qweb': [
|
||||
'static/src/xml/*.xml',
|
||||
|
||||
@@ -18,29 +18,29 @@ class MassMailController(MassMailController):
|
||||
|
||||
is_subscriber = False
|
||||
if email:
|
||||
contacts_count = request.env['mail.mass_mailing.list_contact_rel'].sudo().search_count([('list_id', 'in', [int(list_id)]), ('contact_id.email', '=', email), ('opt_out', '=', False)])
|
||||
contacts_count = request.env['mailing.contact.subscription'].sudo().search_count([('list_id', 'in', [int(list_id)]), ('contact_id.email', '=', email), ('opt_out', '=', False)])
|
||||
is_subscriber = contacts_count > 0
|
||||
|
||||
return {'is_subscriber': is_subscriber, 'email': email}
|
||||
|
||||
@route('/website_mass_mailing/subscribe', type='json', website=True, auth="public")
|
||||
def subscribe(self, list_id, email, **post):
|
||||
List_contact_rel = request.env['mail.mass_mailing.list_contact_rel'].sudo()
|
||||
Contacts = request.env['mail.mass_mailing.contact'].sudo()
|
||||
ContactSubscription = request.env['mailing.contact.subscription'].sudo()
|
||||
Contacts = request.env['mailing.contact'].sudo()
|
||||
name, email = Contacts.get_name_email(email)
|
||||
|
||||
list_contact_rel = List_contact_rel.search([('list_id', '=', int(list_id)), ('contact_id.email', '=', email)], limit=1)
|
||||
if not list_contact_rel:
|
||||
subscription = ContactSubscription.search([('list_id', '=', int(list_id)), ('contact_id.email', '=', email)], limit=1)
|
||||
if not subscription:
|
||||
# inline add_to_list as we've already called half of it
|
||||
contact_id = Contacts.search([('email', '=', email)], limit=1)
|
||||
if not contact_id:
|
||||
contact_id = Contacts.create({'name': name, 'email': email})
|
||||
List_contact_rel.create({'contact_id': contact_id.id, 'list_id': int(list_id)})
|
||||
elif list_contact_rel.opt_out:
|
||||
list_contact_rel.opt_out = False
|
||||
ContactSubscription.create({'contact_id': contact_id.id, 'list_id': int(list_id)})
|
||||
elif subscription.opt_out:
|
||||
subscription.opt_out = False
|
||||
# add email to session
|
||||
request.session['mass_mailing_email'] = email
|
||||
mass_mailing_list = request.env['mail.mass_mailing.list'].sudo().browse(list_id)
|
||||
mass_mailing_list = request.env['mailing.list'].sudo().browse(list_id)
|
||||
return {'toast_content': mass_mailing_list.toast_content}
|
||||
|
||||
@route(['/website_mass_mailing/get_content'], type='json', website=True, auth="public")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import mass_mailing_list
|
||||
from . import mailing_list
|
||||
from . import website_mass_mailing
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class MailingList(models.Model):
|
||||
_inherit = 'mailing.list'
|
||||
|
||||
def _default_toast_content(self):
|
||||
return '<p>Thanks for subscribing!</p>'
|
||||
|
||||
website_popup_ids = fields.One2many('website.mass_mailing.popup', 'mailing_list_id', string="Website Popups")
|
||||
toast_content = fields.Html(default=_default_toast_content, translate=True)
|
||||
+1
-11
@@ -4,16 +4,6 @@
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class MassMailingList(models.Model):
|
||||
_inherit = 'mail.mass_mailing.list'
|
||||
|
||||
def _default_toast_content(self):
|
||||
return '<p>Thanks for subscribing!</p>'
|
||||
|
||||
website_popup_ids = fields.One2many('website.mass_mailing.popup', 'mailing_list_id', string="Website Popups")
|
||||
toast_content = fields.Html(default=_default_toast_content, translate=True)
|
||||
|
||||
|
||||
class MassMailingPopup(models.Model):
|
||||
_name = 'website.mass_mailing.popup'
|
||||
_description = "Mailing list popup"
|
||||
@@ -21,6 +11,6 @@ class MassMailingPopup(models.Model):
|
||||
def _default_popup_content(self):
|
||||
return self.env['ir.ui.view'].render_template('website_mass_mailing.s_newsletter_block')
|
||||
|
||||
mailing_list_id = fields.Many2one('mail.mass_mailing.list')
|
||||
mailing_list_id = fields.Many2one('mailing.list')
|
||||
website_id = fields.Many2one('website')
|
||||
popup_content = fields.Html(string="Website Popup Content", default=_default_popup_content, translate=True, sanitize=False)
|
||||
@@ -30,7 +30,7 @@ options.registry.mailing_list_subscribe = options.Class.extend({
|
||||
'select': _t("Newsletter"),
|
||||
'init': function (field, dialog) {
|
||||
return rpc.query({
|
||||
model: 'mail.mass_mailing.list',
|
||||
model: 'mailing.list',
|
||||
method: 'name_search',
|
||||
args: ['', [['is_public', '=', true]]],
|
||||
context: self.options.recordInfo.context,
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="mailing_list_view_form" model="ir.ui.view">
|
||||
<field name="name">mailing.list.view.form.inherit.website</field>
|
||||
<field name="model">mailing.list</field>
|
||||
<field name="inherit_id" ref="mass_mailing.mailing_list_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//sheet" position="inside">
|
||||
<notebook>
|
||||
<page string="Website popups">
|
||||
<field name="website_popup_ids">
|
||||
<tree>
|
||||
<field name="website_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('oe_title')]" position="after">
|
||||
<group>
|
||||
<field name="toast_content"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
-24
@@ -1,29 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_mail_mass_mailing_list_form" model="ir.ui.view">
|
||||
<field name="name">mail.mass_mailing.list.form</field>
|
||||
<field name="model">mail.mass_mailing.list</field>
|
||||
<field name="inherit_id" ref="mass_mailing.view_mail_mass_mailing_list_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//sheet" position="inside">
|
||||
<notebook>
|
||||
<page string="Website popups">
|
||||
<field name="website_popup_ids">
|
||||
<tree>
|
||||
<field name="website_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('oe_title')]" position="after">
|
||||
<group>
|
||||
<field name="toast_content"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_mail_mass_mailing_popup_form" model="ir.ui.view">
|
||||
<field name="name">website.mass_mailing.popup.form</field>
|
||||
<field name="model">website.mass_mailing.popup</field>
|
||||
Reference in New Issue
Block a user