diff --git a/addons/mass_mailing/__init__.py b/addons/mass_mailing/__init__.py index 88710707227..4e9b019bd7a 100644 --- a/addons/mass_mailing/__init__.py +++ b/addons/mass_mailing/__init__.py @@ -3,4 +3,5 @@ from . import controllers from . import models +from . import report from . import wizard diff --git a/addons/mass_mailing/__manifest__.py b/addons/mass_mailing/__manifest__.py index 1e1986cc211..ab2077b97fb 100644 --- a/addons/mass_mailing/__manifest__.py +++ b/addons/mass_mailing/__manifest__.py @@ -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', diff --git a/addons/mass_mailing/controllers/main.py b/addons/mass_mailing/controllers/main.py index 0d3c563bc6a..02ffe4609c3 100644 --- a/addons/mass_mailing/controllers/main.py +++ b/addons/mass_mailing/controllers/main.py @@ -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//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//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//m/', type='http', auth="public") - def full_url_redirect(self, code, stat_id, **post): + @http.route('/r//m/', 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' diff --git a/addons/mass_mailing/views/mass_mailing_layout.xml b/addons/mass_mailing/data/mail_data.xml similarity index 61% rename from addons/mass_mailing/views/mass_mailing_layout.xml rename to addons/mass_mailing/data/mail_data.xml index eae060d21ea..6c9bd7b591d 100644 --- a/addons/mass_mailing/views/mass_mailing_layout.xml +++ b/addons/mass_mailing/data/mail_data.xml @@ -1,22 +1,5 @@ - - - + - + diff --git a/addons/mass_mailing/data/mass_mailing_data.xml b/addons/mass_mailing/data/mass_mailing_data.xml index 5cf24ae5d9d..37d21928188 100644 --- a/addons/mass_mailing/data/mass_mailing_data.xml +++ b/addons/mass_mailing/data/mass_mailing_data.xml @@ -3,15 +3,15 @@ - + Schedule 10 - + Design 20 - + Sent 30 @@ -19,7 +19,7 @@ Mass Mailing: Process queue - + code model._process_mass_mailing_queue() diff --git a/addons/mass_mailing/data/mass_mailing_demo.xml b/addons/mass_mailing/data/mass_mailing_demo.xml index c1fdc2483f2..af6f9ba57a4 100644 --- a/addons/mass_mailing/data/mass_mailing_demo.xml +++ b/addons/mass_mailing/data/mass_mailing_demo.xml @@ -8,38 +8,38 @@ - + Imported Contacts - + Aristide Antario aa@example.com - + Beverly Bridge bb@example.com - + Carol Cartridge cc@example.com - + David Dawson dd@example.com - + Elsa Ericson ee@example.com - + True @@ -51,7 +51,7 @@ - + Marketing @@ -68,7 +68,7 @@ - + Newsletter 1 done @@ -78,7 +78,6 @@ - email @@ -197,9 +196,9 @@ - + - + 1111000@odoo.com res.partner @@ -208,7 +207,7 @@ - + 1111001@odoo.com res.partner @@ -217,7 +216,7 @@ - + 1111002@odoo.com res.partner @@ -225,7 +224,7 @@ - + 1111003@odoo.com res.partner @@ -233,7 +232,7 @@ - + 1111004@odoo.com res.partner @@ -241,7 +240,7 @@ - + 1111004@odoo.com res.partner @@ -249,7 +248,7 @@ - + 1111004@odoo.com res.partner @@ -265,7 +264,7 @@ use="code"/> 100.01.02.03 BE - + 100.01.02.03 BE - + 100.01.02.04 BE - + 100.01.02.04 BE - + 100.01.02.05 BE - + diff --git a/addons/mass_mailing/models/__init__.py b/addons/mass_mailing/models/__init__.py index c542abccfba..1e852942767 100644 --- a/addons/mass_mailing/models/__init__.py +++ b/addons/mass_mailing/models/__init__.py @@ -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 diff --git a/addons/mass_mailing/models/link_tracker.py b/addons/mass_mailing/models/link_tracker.py index 2b20b007a4b..ce3897e006a 100644 --- a/addons/mass_mailing/models/link_tracker.py +++ b/addons/mass_mailing/models/link_tracker.py @@ -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 diff --git a/addons/mass_mailing/models/mail_mail.py b/addons/mass_mailing/models/mail_mail.py index a4de6be6972..79ab5c2d39e 100644 --- a/addons/mass_mailing/models/mail_mail.py +++ b/addons/mass_mailing/models/mail_mail.py @@ -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) diff --git a/addons/mass_mailing/models/mail_thread.py b/addons/mass_mailing/models/mail_thread.py index 5195672eead..28b02921801 100644 --- a/addons/mass_mailing/models/mail_thread.py +++ b/addons/mass_mailing/models/mail_thread.py @@ -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.') diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mailing.py similarity index 65% rename from addons/mass_mailing/models/mass_mailing.py rename to addons/mass_mailing/models/mailing.py index f4f598cf3c9..7817237818e 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mailing.py @@ -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 unsubscribed from %s mailing list(s)') \ - if value else _('The recipient subscribed to %s 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 unsubscribed from %s mailing list(s)') \ + if value else _('The recipient subscribed to %s 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 diff --git a/addons/mass_mailing/models/mailing_contact.py b/addons/mass_mailing/models/mailing_contact.py new file mode 100644 index 00000000000..966a5f29aaa --- /dev/null +++ b/addons/mass_mailing/models/mailing_contact.py @@ -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 + } diff --git a/addons/mass_mailing/models/mailing_list.py b/addons/mass_mailing/models/mailing_list.py new file mode 100644 index 00000000000..ad78b7ad85f --- /dev/null +++ b/addons/mass_mailing/models/mailing_list.py @@ -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'} diff --git a/addons/mass_mailing/models/mass_mailing_stats.py b/addons/mass_mailing/models/mailing_trace.py similarity index 77% rename from addons/mass_mailing/models/mass_mailing_stats.py rename to addons/mass_mailing/models/mailing_trace.py index f0d8a763438..7f8ce107d14 100644 --- a/addons/mass_mailing/models/mass_mailing_stats.py +++ b/addons/mass_mailing/models/mailing_trace.py @@ -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 diff --git a/addons/mass_mailing/report/__init__.py b/addons/mass_mailing/report/__init__.py new file mode 100644 index 00000000000..cd346c5e5ca --- /dev/null +++ b/addons/mass_mailing/report/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import mailing_trace_report diff --git a/addons/mass_mailing/models/mass_mailing_report.py b/addons/mass_mailing/report/mailing_trace_report.py similarity index 74% rename from addons/mass_mailing/models/mass_mailing_report.py rename to addons/mass_mailing/report/mailing_trace_report.py index de63344389c..be48a863b1e 100644 --- a/addons/mass_mailing/models/mass_mailing_report.py +++ b/addons/mass_mailing/report/mailing_trace_report.py @@ -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) diff --git a/addons/mass_mailing/views/mass_mailing_report_views.xml b/addons/mass_mailing/report/mailing_trace_report_views.xml similarity index 74% rename from addons/mass_mailing/views/mass_mailing_report_views.xml rename to addons/mass_mailing/report/mailing_trace_report_views.xml index 6924fdc0433..0840fcb2254 100644 --- a/addons/mass_mailing/views/mass_mailing_report_views.xml +++ b/addons/mass_mailing/report/mailing_trace_report_views.xml @@ -1,8 +1,8 @@ - - mail.statistics.report.pivot - mail.statistics.report + + mailing.trace.report.view.pivot + mailing.trace.report @@ -16,9 +16,9 @@ - - mail.statistics.report.graph - mail.statistics.report + + mailing.trace.report.view.graph + mailing.trace.report @@ -27,9 +27,9 @@ - - mail.statistics.report.search - mail.statistics.report + + mailing.trace.report.view.search + mailing.trace.report @@ -51,14 +51,14 @@ - + Mass Mailing Analysis - mail.statistics.report + mailing.trace.report graph,pivot 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. + parent="mass_mailing_menu_root" action="mailing_trace_report_action" groups="mass_mailing.group_mass_mailing_user"/> diff --git a/addons/mass_mailing/security/ir.model.access.csv b/addons/mass_mailing/security/ir.model.access.csv index 7e3887ea576..d131c63a741 100644 --- a/addons/mass_mailing/security/ir.model.access.csv +++ b/addons/mass_mailing/security/ir.model.access.csv @@ -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 \ No newline at end of file diff --git a/addons/mass_mailing/static/src/js/mass_mailing.js b/addons/mass_mailing/static/src/js/mass_mailing.js index 404ed6e554b..5d156c3cd65 100644 --- a/addons/mass_mailing/static/src/js/mass_mailing.js +++ b/addons/mass_mailing/static/src/js/mass_mailing.js @@ -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; } }, diff --git a/addons/mass_mailing/static/tests/mass_mailing_html_tests.js b/addons/mass_mailing/static/tests/mass_mailing_html_tests.js index 9a5f9ce259c..bf1d725dd90 100644 --- a/addons/mass_mailing/static/tests/mass_mailing_html_tests.js +++ b/addons/mass_mailing/static/tests/mass_mailing_html_tests.js @@ -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: '
' + ' ' + '
' + diff --git a/addons/mass_mailing/tests/test_mass_mailing_list_merge.py b/addons/mass_mailing/tests/test_mass_mailing_list_merge.py index 3408989d77e..fe8feba73ec 100644 --- a/addons/mass_mailing/tests/test_mass_mailing_list_merge.py +++ b/addons/mass_mailing/tests/test_mass_mailing_list_merge.py @@ -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', diff --git a/addons/mass_mailing/tests/test_mass_mailing_shortener.py b/addons/mass_mailing/tests/test_mass_mailing_shortener.py index d279208264d..bc55668579c 100644 --- a/addons/mass_mailing/tests/test_mass_mailing_shortener.py +++ b/addons/mass_mailing/tests/test_mass_mailing_shortener.py @@ -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 ", - "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, diff --git a/addons/mass_mailing/views/assets.xml b/addons/mass_mailing/views/assets.xml new file mode 100644 index 00000000000..03b1a1415bc --- /dev/null +++ b/addons/mass_mailing/views/assets.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + - diff --git a/addons/mass_mailing/views/unsubscribe_templates.xml b/addons/mass_mailing/views/mass_mailing_templates_portal.xml similarity index 100% rename from addons/mass_mailing/views/unsubscribe_templates.xml rename to addons/mass_mailing/views/mass_mailing_templates_portal.xml diff --git a/addons/mass_mailing/views/snippets_themes.xml b/addons/mass_mailing/views/snippets_themes.xml index 7e908e12033..ac6c75ba128 100644 --- a/addons/mass_mailing/views/snippets_themes.xml +++ b/addons/mass_mailing/views/snippets_themes.xml @@ -769,4 +769,52 @@ + + + diff --git a/addons/mass_mailing/views/snippets_themes_options.xml b/addons/mass_mailing/views/snippets_themes_options.xml deleted file mode 100644 index 7700dd00d14..00000000000 --- a/addons/mass_mailing/views/snippets_themes_options.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - diff --git a/addons/mass_mailing/wizard/__init__.py b/addons/mass_mailing/wizard/__init__.py index d501dfb411a..d4f1e912d5e 100644 --- a/addons/mass_mailing/wizard/__init__.py +++ b/addons/mass_mailing/wizard/__init__.py @@ -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 diff --git a/addons/mass_mailing/wizard/mail_compose_message.py b/addons/mass_mailing/wizard/mail_compose_message.py index 3f5c0c03e2e..d8be92aafc4 100644 --- a/addons/mass_mailing/wizard/mail_compose_message.py +++ b/addons/mass_mailing/wizard/mail_compose_message.py @@ -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, diff --git a/addons/mass_mailing/wizard/mass_mailing_list_merge.py b/addons/mass_mailing/wizard/mailing_list_merge.py similarity index 79% rename from addons/mass_mailing/wizard/mass_mailing_list_merge.py rename to addons/mass_mailing/wizard/mailing_list_merge.py index 9a1a0f4409f..0ef97b12fa3 100644 --- a/addons/mass_mailing/wizard/mass_mailing_list_merge.py +++ b/addons/mass_mailing/wizard/mailing_list_merge.py @@ -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) diff --git a/addons/mass_mailing/wizard/mass_mailing_list_merge_views.xml b/addons/mass_mailing/wizard/mailing_list_merge_views.xml similarity index 75% rename from addons/mass_mailing/wizard/mass_mailing_list_merge_views.xml rename to addons/mass_mailing/wizard/mailing_list_merge_views.xml index c1a1ebe3924..d5c9300483c 100644 --- a/addons/mass_mailing/wizard/mass_mailing_list_merge_views.xml +++ b/addons/mass_mailing/wizard/mailing_list_merge_views.xml @@ -1,9 +1,9 @@ - - mass.mailing.list.merge.form - mass.mailing.list.merge + + mailing.list.merge.form + mailing.list.merge @@ -26,8 +26,8 @@ - diff --git a/addons/mass_mailing/wizard/mass_mailing_schedule_date.py b/addons/mass_mailing/wizard/mailing_mailing_schedule_date.py similarity index 80% rename from addons/mass_mailing/wizard/mass_mailing_schedule_date.py rename to addons/mass_mailing/wizard/mailing_mailing_schedule_date.py index c9a864ac6ca..1d239c51022 100644 --- a/addons/mass_mailing/wizard/mass_mailing_schedule_date.py +++ b/addons/mass_mailing/wizard/mailing_mailing_schedule_date.py @@ -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): diff --git a/addons/mass_mailing/wizard/mass_mailing_schedule_date_views.xml b/addons/mass_mailing/wizard/mailing_mailing_schedule_date_views.xml similarity index 68% rename from addons/mass_mailing/wizard/mass_mailing_schedule_date_views.xml rename to addons/mass_mailing/wizard/mailing_mailing_schedule_date_views.xml index 46b3244dbb4..4840b16039d 100644 --- a/addons/mass_mailing/wizard/mass_mailing_schedule_date_views.xml +++ b/addons/mass_mailing/wizard/mailing_mailing_schedule_date_views.xml @@ -1,8 +1,8 @@ - - mass.mailing.schedule.date.view.form - mass.mailing.schedule.date + + mailing.mailing.schedule.date.view.form + mailing.mailing.schedule.date @@ -18,9 +18,9 @@ - + When do you want to send your mailing? - mass.mailing.schedule.date + mailing.mailing.schedule.date ir.actions.act_window form new diff --git a/addons/mass_mailing/wizard/test_mailing.py b/addons/mass_mailing/wizard/mailing_mailing_test.py similarity index 92% rename from addons/mass_mailing/wizard/test_mailing.py rename to addons/mass_mailing/wizard/mailing_mailing_test.py index ba66510ca68..54424262563 100644 --- a/addons/mass_mailing/wizard/test_mailing.py +++ b/addons/mass_mailing/wizard/mailing_mailing_test.py @@ -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() diff --git a/addons/mass_mailing/wizard/test_mailing_views.xml b/addons/mass_mailing/wizard/mailing_mailing_test_views.xml similarity index 84% rename from addons/mass_mailing/wizard/test_mailing_views.xml rename to addons/mass_mailing/wizard/mailing_mailing_test_views.xml index bc369863604..901f5c9105d 100644 --- a/addons/mass_mailing/wizard/test_mailing_views.xml +++ b/addons/mass_mailing/wizard/mailing_mailing_test_views.xml @@ -2,8 +2,8 @@ - mail.mass_mailing.test.form - mail.mass_mailing.test + mailing.mailing.test.form + mailing.mailing.test

@@ -22,7 +22,7 @@ Mailing Test - mail.mass_mailing.test + mailing.mailing.test form new diff --git a/addons/mass_mailing_crm/__manifest__.py b/addons/mass_mailing_crm/__manifest__.py index 083ebe6240e..e6e3d528bb0 100644 --- a/addons/mass_mailing_crm/__manifest__.py +++ b/addons/mass_mailing_crm/__manifest__.py @@ -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', diff --git a/addons/mass_mailing_crm/data/mass_mailing_demo.xml b/addons/mass_mailing_crm/data/mass_mailing_demo.xml index 195ec7b5791..b124fbbc66c 100644 --- a/addons/mass_mailing_crm/data/mass_mailing_demo.xml +++ b/addons/mass_mailing_crm/data/mass_mailing_demo.xml @@ -1,7 +1,7 @@ - + Lead Recall We want to hear form you ! in_queue @@ -10,7 +10,6 @@ - [] email diff --git a/addons/mass_mailing_crm/models/__init__.py b/addons/mass_mailing_crm/models/__init__.py index 972aef5812b..9733ab5351b 100644 --- a/addons/mass_mailing_crm/models/__init__.py +++ b/addons/mass_mailing_crm/models/__init__.py @@ -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 diff --git a/addons/mass_mailing_crm/models/mass_mailing.py b/addons/mass_mailing_crm/models/mailing_mailing.py similarity index 97% rename from addons/mass_mailing_crm/models/mass_mailing.py rename to addons/mass_mailing_crm/models/mailing_mailing.py index 2faede45ed0..a740a79968c 100644 --- a/addons/mass_mailing_crm/models/mass_mailing.py +++ b/addons/mass_mailing_crm/models/mailing_mailing.py @@ -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') diff --git a/addons/mass_mailing_crm/views/mass_mailing_views.xml b/addons/mass_mailing_crm/views/mailing_mailing_views.xml similarity index 86% rename from addons/mass_mailing_crm/views/mass_mailing_views.xml rename to addons/mass_mailing_crm/views/mailing_mailing_views.xml index f0d2e33ebc9..d2169fa3454 100644 --- a/addons/mass_mailing_crm/views/mass_mailing_views.xml +++ b/addons/mass_mailing_crm/views/mailing_mailing_views.xml @@ -1,8 +1,8 @@ - - mail.mass_mailing.view.form.inherit.crm - mail.mass_mailing + + mailing.mailing.view.form.inherit.crm + mailing.mailing diff --git a/addons/mass_mailing_event/models/__init__.py b/addons/mass_mailing_event/models/__init__.py index 85b632eb8a3..87f0737c96a 100644 --- a/addons/mass_mailing_event/models/__init__.py +++ b/addons/mass_mailing_event/models/__init__.py @@ -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 diff --git a/addons/mass_mailing_event/models/event.py b/addons/mass_mailing_event/models/event.py index 5ce4ec76b27..e0443acc7d3 100644 --- a/addons/mass_mailing_event/models/event.py +++ b/addons/mass_mailing_event/models/event.py @@ -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( diff --git a/addons/mass_mailing_event/models/mass_mailing.py b/addons/mass_mailing_event/models/mailing_mailing.py similarity index 87% rename from addons/mass_mailing_event/models/mass_mailing.py rename to addons/mass_mailing_event/models/mailing_mailing.py index a9849ad8deb..4a023131f68 100644 --- a/addons/mass_mailing_event/models/mass_mailing.py +++ b/addons/mass_mailing_event/models/mailing_mailing.py @@ -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', '[]') diff --git a/addons/mass_mailing_event_track/models/event.py b/addons/mass_mailing_event_track/models/event.py index 56b76ea9766..c3395986fbd 100644 --- a/addons/mass_mailing_event_track/models/event.py +++ b/addons/mass_mailing_event_track/models/event.py @@ -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( diff --git a/addons/mass_mailing_sale/__manifest__.py b/addons/mass_mailing_sale/__manifest__.py index 74c8e741c63..c43b6142eda 100644 --- a/addons/mass_mailing_sale/__manifest__.py +++ b/addons/mass_mailing_sale/__manifest__.py @@ -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', diff --git a/addons/mass_mailing_sale/data/mass_mailing_demo.xml b/addons/mass_mailing_sale/data/mass_mailing_demo.xml index 9a5524c532f..5e6daf0957b 100644 --- a/addons/mass_mailing_sale/data/mass_mailing_demo.xml +++ b/addons/mass_mailing_sale/data/mass_mailing_demo.xml @@ -1,7 +1,7 @@ - + Sale Promotion 1 Our last promotions, just for you ! in_queue @@ -10,7 +10,6 @@ - [] email diff --git a/addons/mass_mailing_sale/models/__init__.py b/addons/mass_mailing_sale/models/__init__.py index 972aef5812b..9733ab5351b 100644 --- a/addons/mass_mailing_sale/models/__init__.py +++ b/addons/mass_mailing_sale/models/__init__.py @@ -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 diff --git a/addons/mass_mailing_sale/models/mass_mailing.py b/addons/mass_mailing_sale/models/mailing_mailing.py similarity index 97% rename from addons/mass_mailing_sale/models/mass_mailing.py rename to addons/mass_mailing_sale/models/mailing_mailing.py index 85b797a839e..491fe316ec3 100644 --- a/addons/mass_mailing_sale/models/mass_mailing.py +++ b/addons/mass_mailing_sale/models/mailing_mailing.py @@ -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') diff --git a/addons/mass_mailing_sale/views/mass_mailing_views.xml b/addons/mass_mailing_sale/views/mailing_mailing_views.xml similarity index 84% rename from addons/mass_mailing_sale/views/mass_mailing_views.xml rename to addons/mass_mailing_sale/views/mailing_mailing_views.xml index 46d17296926..3486adbf1b2 100644 --- a/addons/mass_mailing_sale/views/mass_mailing_views.xml +++ b/addons/mass_mailing_sale/views/mailing_mailing_views.xml @@ -1,8 +1,8 @@ - - mail.mass_mailing.view.form.inherit.sale - mail.mass_mailing + + mailing.mailing.view.form.inherit.sale + mailing.mailing diff --git a/addons/test_mass_mailing/tests/test_composer.py b/addons/test_mass_mailing/tests/test_composer.py index 51d70b094e6..4307bc95591 100644 --- a/addons/test_mass_mailing/tests/test_composer.py +++ b/addons/test_mass_mailing/tests/test_composer.py @@ -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', diff --git a/addons/test_mass_mailing/tests/test_link.py b/addons/test_mass_mailing/tests/test_link.py index bbe731413bf..b081c384709 100644 --- a/addons/test_mass_mailing/tests/test_link.py +++ b/addons/test_mass_mailing/tests/test_link.py @@ -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) diff --git a/addons/test_mass_mailing/tests/test_mail_auto_blacklist.py b/addons/test_mass_mailing/tests/test_mail_auto_blacklist.py index ac04a835847..ff9bc5268c1 100644 --- a/addons/test_mass_mailing/tests/test_mail_auto_blacklist.py +++ b/addons/test_mass_mailing/tests/test_mail_auto_blacklist.py @@ -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 diff --git a/addons/test_mass_mailing/tests/test_mass_mailing.py b/addons/test_mass_mailing/tests/test_mass_mailing.py index 28ba0596e6b..e300fb02ae4 100644 --- a/addons/test_mass_mailing/tests/test_mass_mailing.py +++ b/addons/test_mass_mailing/tests/test_mass_mailing.py @@ -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, diff --git a/addons/test_mass_mailing/tests/test_performance.py b/addons/test_mass_mailing/tests/test_performance.py index a3b6699e2be..b3ec164653d 100644 --- a/addons/test_mass_mailing/tests/test_performance.py +++ b/addons/test_mass_mailing/tests/test_performance.py @@ -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': '

Hello quuxUnsubscribe

', @@ -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': '

Hello quuxUnsubscribe

', @@ -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) diff --git a/addons/website_mass_mailing/__manifest__.py b/addons/website_mass_mailing/__manifest__.py index 11d24b6d541..11c494b5673 100644 --- a/addons/website_mass_mailing/__manifest__.py +++ b/addons/website_mass_mailing/__manifest__.py @@ -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', diff --git a/addons/website_mass_mailing/controllers/main.py b/addons/website_mass_mailing/controllers/main.py index fc49d23d83b..dc34504426c 100644 --- a/addons/website_mass_mailing/controllers/main.py +++ b/addons/website_mass_mailing/controllers/main.py @@ -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") diff --git a/addons/website_mass_mailing/models/__init__.py b/addons/website_mass_mailing/models/__init__.py index ecf18af5dd6..e8ef8b35d78 100644 --- a/addons/website_mass_mailing/models/__init__.py +++ b/addons/website_mass_mailing/models/__init__.py @@ -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 diff --git a/addons/website_mass_mailing/models/mailing_list.py b/addons/website_mass_mailing/models/mailing_list.py new file mode 100644 index 00000000000..0857563a814 --- /dev/null +++ b/addons/website_mass_mailing/models/mailing_list.py @@ -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 '

Thanks for subscribing!

' + + 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) diff --git a/addons/website_mass_mailing/models/mass_mailing_list.py b/addons/website_mass_mailing/models/website_mass_mailing.py similarity index 56% rename from addons/website_mass_mailing/models/mass_mailing_list.py rename to addons/website_mass_mailing/models/website_mass_mailing.py index a10344ecddb..fdad7c16bc2 100644 --- a/addons/website_mass_mailing/models/mass_mailing_list.py +++ b/addons/website_mass_mailing/models/website_mass_mailing.py @@ -4,16 +4,6 @@ from odoo import fields, models -class MassMailingList(models.Model): - _inherit = 'mail.mass_mailing.list' - - def _default_toast_content(self): - return '

Thanks for subscribing!

' - - 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) diff --git a/addons/website_mass_mailing/static/src/js/website_mass_mailing.editor.js b/addons/website_mass_mailing/static/src/js/website_mass_mailing.editor.js index 01ee423b6bd..526c7e35fec 100644 --- a/addons/website_mass_mailing/static/src/js/website_mass_mailing.editor.js +++ b/addons/website_mass_mailing/static/src/js/website_mass_mailing.editor.js @@ -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, diff --git a/addons/website_mass_mailing/views/mailing_list_views.xml b/addons/website_mass_mailing/views/mailing_list_views.xml new file mode 100644 index 00000000000..e5144cf63a6 --- /dev/null +++ b/addons/website_mass_mailing/views/mailing_list_views.xml @@ -0,0 +1,26 @@ + + + + mailing.list.view.form.inherit.website + mailing.list + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/website_mass_mailing/views/mass_mailing_view.xml b/addons/website_mass_mailing/views/website_mass_mailing_views.xml similarity index 50% rename from addons/website_mass_mailing/views/mass_mailing_view.xml rename to addons/website_mass_mailing/views/website_mass_mailing_views.xml index abfc03f8a8a..5c9a34ac6c3 100644 --- a/addons/website_mass_mailing/views/mass_mailing_view.xml +++ b/addons/website_mass_mailing/views/website_mass_mailing_views.xml @@ -1,29 +1,5 @@ - - mail.mass_mailing.list.form - mail.mass_mailing.list - - - - - - - - - - - - - - - - - - - - - website.mass_mailing.popup.form website.mass_mailing.popup