Начальное наполнение

This commit is contained in:
parent 067ad00fea
commit 082399ed82
353 changed files with 381396 additions and 1 deletions

View File

@ -1,2 +1,97 @@
# mass_mailing
Odoo Mass Mailing
-----------------
Easily send mass mailing to your leads, opportunities or customers
with Odoo <a href="https://www.odoo.com/app/email-marketing">Email Marketing</a>. Track
marketing campaigns performance to improve conversion rates. Design
professional emails and reuse templates in a few clicks.
Send Professional Emails
------------------------
Import database of prospects or filter on existing leads, opportunities and
customers in just a few clicks.
Define email templates to reuse content or specific design for your newsletter.
Setup several email servers with their own IP/domain to optimise opening rates.
Organize Marketing Campaigns
----------------------------
Design, Send, Track by Campaigns with our <a href="https://www.odoo.com/app/email-marketing">Lead Automation</a> app.
Get real time statistics on campaigns performance to improve your conversion
rate. Track mails sent, received, opened and answered.
Easily manage your marketing campaigns, discussion groups, leads and
opportunities in one simple and powerful platform.
Integrated with Odoo Apps
-------------------------
Get access to mass mailing features from every Odoo app to improve the way your
users communicate.
Send template of emails from Odoo <a href="https://www.odoo.com/app/email-marketing">CRM opportunities</a>, select leads based
on marketing segments, send <a href="https://www.odoo.com/app/recruitment">job offers</a> and automate
answers to applicants, reuse email template in the lead automation marketing
campaigns.
Answers to your emails appears automatically in the history of every document
with the social network module.
Clean Your Lead Database
------------------------
Get a clean lead database that improves over the time using the performance of
your mails. Odoo handle bounce mails efficiently, flag erroneous leads
accordingly and gives you statistics on the quality of your leads.
One click emails send
---------------------
The marketing department will love working on campaigns. But you can also give
a one click mass mailing facility to all others users on their own prospects or
documents.
Select a few documents (e.g. leads, support tickets, suppliers, applicants,
...) and send emails to their contacts in one click, reusing existing emails
templates.
Follow-up On Answers
--------------------
The chatter feature enables you to communicate faster and more efficiently with
your customer. Get documents created automatically (leads, opportunities,
tasks, ...) based on answers to your mass mailing campaigns Follow the
discussion directly on the business documents within Odoo or via email.
Get all the negotiations and discussions attached to the right document and
relevent managers notified on specific events.
Campaigns Dashboard
-------------------
Get the insights you need to make smarter marketing campaign. Track statistics
per campaign: bounce rates, sent mails, best content, etc. The clear dashboards
gives you a direct overview of your campaign performance.
Fully Integrated With Others Apps
---------------------------------
Define automation rules (e.g. ask a salesperson to call, send an email, ...)
based on triggers (no activity since 20 days, answered a promotional email,
etc.)
Optimize campaigns from lead to close, on every channel. Make smarter decisions
about where to invest and show the impact of your marketing activities on your
company's bottom line.
Integrate a contact form in your website easily. Forms submissions create leads
automatically in Odoo CRM. Leads can be used in marketing campaigns.
Manage your <a href="https://www.odoo.com/app/crm">sales funnel</a> with no
effort. Attract leads, follow-up on phone calls and meetings. Analyse the
quality of your leads to make informed decisions and save time by integrating
emails directly into the application.

7
__init__.py Normal file
View File

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controllers
from . import models
from . import report
from . import wizard

174
__manifest__.py Normal file
View File

@ -0,0 +1,174 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Email Marketing',
'summary': 'Design, send and track emails',
'version': '2.7',
'sequence': 60,
'website': 'https://www.odoo.com/app/email-marketing',
'category': 'Marketing/Email Marketing',
'depends': [
'contacts',
'mail',
'utm',
'link_tracker',
'web_editor',
'social_media',
'web_tour',
'digest',
],
'data': [
'security/res_groups_data.xml',
'security/ir.model.access.csv',
'data/digest_data.xml',
'data/ir_attachment_data.xml',
'data/ir_config_parameter_data.xml',
'data/ir_cron_data.xml',
'data/ir_module_data.xml',
'data/mailing_data_templates.xml',
'data/mailing_list_contact.xml',
'data/mailing_subscription_optout.xml',
'data/mailing_subscription.xml',
'data/res_users_data.xml',
'wizard/mail_compose_message_views.xml',
'wizard/mailing_contact_import_views.xml',
'wizard/mailing_contact_to_list_views.xml',
'wizard/mailing_list_merge_views.xml',
'wizard/mailing_mailing_test_views.xml',
'wizard/mailing_mailing_schedule_date_views.xml',
'report/mailing_trace_report_views.xml',
'views/mail_blacklist_views.xml',
'views/mailing_filter_views.xml',
'views/mailing_mobile_preview_content.xml',
'views/mailing_trace_views.xml',
'views/link_tracker_views.xml',
'views/mailing_contact_views.xml',
'views/mailing_list_views.xml',
'views/mailing_mailing_views.xml',
'views/mailing_subscription_views.xml',
'views/mailing_subscription_optout_views.xml',
'views/res_config_settings_views.xml',
'views/utm_campaign_views.xml',
'views/mailing_menus.xml',
'views/mailing_templates_portal_layouts.xml',
'views/mailing_templates_portal_management.xml',
'views/mailing_templates_portal_unsubscribe.xml',
'views/themes_templates.xml',
'views/snippets_themes.xml',
'views/snippets/s_alert.xml',
'views/snippets/s_blockquote.xml',
'views/snippets/s_call_to_action.xml',
'views/snippets/s_coupon_code.xml',
'views/snippets/s_cover.xml',
'views/snippets/s_color_blocks_2.xml',
'views/snippets/s_company_team.xml',
'views/snippets/s_comparisons.xml',
'views/snippets/s_event.xml',
'views/snippets/s_features.xml',
'views/snippets/s_features_grid.xml',
'views/snippets/s_hr.xml',
'views/snippets/s_image_text.xml',
'views/snippets/s_masonry_block.xml',
'views/snippets/s_media_list.xml',
'views/snippets/s_numbers.xml',
'views/snippets/s_picture.xml',
'views/snippets/s_product_list.xml',
'views/snippets/s_rating.xml',
'views/snippets/s_references.xml',
'views/snippets/s_showcase.xml',
'views/snippets/s_text_block.xml',
'views/snippets/s_text_highlight.xml',
'views/snippets/s_text_image.xml',
'views/snippets/s_three_columns.xml',
'views/snippets/s_title.xml',
],
'demo': [
'demo/utm.xml',
'demo/mailing_list_contact.xml',
'demo/mailing_subscription.xml',
'demo/mailing_mailing.xml',
'demo/mailing_trace.xml',
],
'application': True,
'assets': {
'mass_mailing.iframe_css_assets_edit': [
('include', 'mass_mailing.assets_mail_themes'),
('include', 'web.assets_frontend'),
('after', 'web/static/lib/bootstrap/scss/_variables.scss', 'mass_mailing/static/src/scss/mass_mailing.ui.scss'),
('include', 'web_editor.backend_assets_wysiwyg'),
('include', 'web_editor.assets_legacy_wysiwyg'),
'mass_mailing/static/src/scss/mass_mailing_mail.scss',
],
'mass_mailing.iframe_css_assets_readonly': [
'mass_mailing/static/src/scss/mass_mailing_mail.scss',
'mass_mailing/static/src/css/basic_theme_readonly.css'
],
'mass_mailing.mailing_assets': [
'mass_mailing/static/src/scss/mailing_portal.scss',
'mass_mailing/static/src/js/mailing_portal_subscription.js',
'mass_mailing/static/src/js/mailing_portal_subscription_blocklist.js',
'mass_mailing/static/src/js/mailing_portal_subscription_feedback.js',
'mass_mailing/static/src/js/mailing_portal_subscription_form.js',
'mass_mailing/static/src/xml/mailing_portal_subscription_blocklist.xml',
'mass_mailing/static/src/xml/mailing_portal_subscription_feedback.xml',
'mass_mailing/static/src/xml/mailing_portal_subscription_form.xml',
],
'web_editor.backend_assets_wysiwyg': [
'mass_mailing/static/src/js/mass_mailing_wysiwyg.js',
'mass_mailing/static/src/scss/mass_mailing.wysiwyg.scss',
],
'web.assets_backend': [
'mass_mailing/static/src/scss/mailing_filter_widget.scss',
'mass_mailing/static/src/scss/mass_mailing.scss',
'mass_mailing/static/src/scss/mass_mailing_mobile.scss',
'mass_mailing/static/src/scss/mass_mailing_mobile_preview.scss',
'mass_mailing/static/src/css/email_template.css',
'mass_mailing/static/src/js/mailing_m2o_filter.js',
'mass_mailing/static/src/js/mass_mailing_design_constants.js',
'mass_mailing/static/src/js/mass_mailing_mobile_preview.js',
'mass_mailing/static/src/js/mass_mailing_html_field.js',
'mass_mailing/static/src/js/mailing_mailing_view_form_full_width.js',
'mass_mailing/static/src/xml/mailing_filter_widget.xml',
'mass_mailing/static/src/xml/mass_mailing.xml',
'mass_mailing/static/src/xml/mass_mailing_mobile_preview.xml',
'mass_mailing/static/src/js/tours/**/*',
],
'mass_mailing.assets_mail_themes': [
'mass_mailing/static/src/scss/themes/**/*',
],
'mass_mailing.assets_mail_themes_edition': [
('include', 'web._assets_helpers'),
'web/static/src/scss/pre_variables.scss',
'web/static/lib/bootstrap/scss/_variables.scss',
'mass_mailing/static/src/scss/mass_mailing.ui.scss',
],
'mass_mailing.assets_wysiwyg': [
'mass_mailing/static/src/js/mass_mailing_snippets.js',
'mass_mailing/static/src/snippets/s_masonry_block/options.js',
'mass_mailing/static/src/snippets/s_media_list/options.js',
'mass_mailing/static/src/snippets/s_showcase/options.js',
'mass_mailing/static/src/snippets/s_rating/options.js'
],
'web_editor.assets_legacy_wysiwyg': [
'mass_mailing/static/src/js/snippets.editor.js',
],
'web.assets_frontend': [
'mass_mailing/static/src/js/tours/**/*',
],
'web.assets_tests': [
'mass_mailing/static/tests/tours/**/*',
],
'web.qunit_suite_tests': [
'mass_mailing/static/tests/mass_mailing_favourite_filter_tests.js',
'mass_mailing/static/src/js/mass_mailing_snippets.js',
'mass_mailing/static/src/snippets/s_media_list/options.js',
'mass_mailing/static/src/snippets/s_showcase/options.js',
'mass_mailing/static/src/snippets/s_rating/options.js',
'mass_mailing/static/tests/mass_mailing_html_tests.js',
'mass_mailing/static/tests/mailing_mailing_view_form_tests.js',
],
},
'license': 'LGPL-3',
}

5
controllers/__init__.py Normal file
View File

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import legacy
from . import main

21
controllers/legacy.py Normal file
View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import werkzeug
from odoo import http
from odoo.http import request
class MailingLegacy(http.Controller):
""" Retro compatibility layer for legacy endpoint"""
@http.route(['/mail/mailing/<int:mailing_id>/unsubscribe'], type='http', website=True, auth='public')
def mailing_unsubscribe(self, mailing_id, email=None, res_id=None, token="", **post):
""" Old route, using mail/mailing prefix, and outdated parameter names """
params = werkzeug.urls.url_encode(
dict(**post, document_id=res_id, email=email, hash_token=token)
)
return request.redirect(
f'/mailing/{mailing_id}/unsubscribe?{params}'
)

491
controllers/main.py Normal file
View File

@ -0,0 +1,491 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import werkzeug
from datetime import timedelta
from markupsafe import Markup, escape
from werkzeug.exceptions import BadRequest, NotFound, Unauthorized
from odoo import _, fields, http, tools
from odoo.http import request, Response
from odoo.tools import consteq
class MassMailController(http.Controller):
def _check_mailing_email_token(self, mailing_id, document_id, email, hash_token,
required_mailing_id=False):
""" Return the mailing based on given credentials, sudo-ed. Raises if
there is an issue fetching it.
Specific use case
* hash_token is always required for public users, no generic page is
available for them;
* hash_token is not required for generic page for logged user, aka
if no mailing_id is given;
* hash_token is not required for mailing specific page if the user
is a mailing user;
* hash_token is not required for generic page for logged user, aka
if no mailing_id is given and if mailing_id is not required;
* hash_token always requires the triplet mailing_id, email and
document_id, as it indicates it comes from a mailing email and
is used when comparing hashes;
"""
if not hash_token:
if request.env.user._is_public():
raise BadRequest()
if mailing_id and not request.env.user.has_group('mass_mailing.group_mass_mailing_user'):
raise BadRequest()
if hash_token and (not mailing_id or not email or not document_id):
raise BadRequest()
if mailing_id:
mailing_sudo = request.env['mailing.mailing'].sudo().browse(mailing_id)
if not mailing_sudo.exists():
raise NotFound()
if hash_token and not consteq(mailing_sudo._generate_mailing_recipient_token(document_id, email), hash_token):
raise Unauthorized()
else:
if required_mailing_id:
raise BadRequest()
mailing_sudo = request.env['mailing.mailing'].sudo()
return mailing_sudo
def _fetch_blocklist_record(self, email):
if not email or not tools.email_normalize(email):
return None
return request.env['mail.blacklist'].sudo().with_context(
active_test=False
).search(
[('email', '=', tools.email_normalize(email))]
)
def _fetch_contacts(self, email):
if not email or not tools.email_normalize(email):
return request.env['mailing.contact']
return request.env['mailing.contact'].sudo().search(
[('email_normalized', '=', tools.email_normalize(email))]
)
def _fetch_subscription_optouts(self):
return request.env['mailing.subscription.optout'].sudo().search([])
def _fetch_user_information(self, email, hash_token):
if hash_token or request.env.user._is_public():
return email, hash_token
return request.env.user.email_normalized, None
# ------------------------------------------------------------
# SUBSCRIPTION MANAGEMENT
# ------------------------------------------------------------
@http.route('/mailing/my', type='http', website=True, auth='user')
def mailing_my(self):
email, _hash_token = self._fetch_user_information(None, None)
if not email:
raise Unauthorized()
render_values = self._prepare_mailing_subscription_values(
request.env['mailing.mailing'], False, email, None
)
render_values.update(feedback_enabled=False)
return request.render(
'mass_mailing.page_mailing_unsubscribe',
render_values
)
@http.route(['/mailing/<int:mailing_id>/unsubscribe'], type='http', website=True, auth='public')
def mailing_unsubscribe(self, mailing_id, document_id=None, email=None, hash_token=None):
email_found, hash_token_found = self._fetch_user_information(email, hash_token)
try:
mailing_sudo = self._check_mailing_email_token(
mailing_id, document_id, email_found, hash_token_found,
required_mailing_id=True
)
except NotFound as e: # avoid leaking ID existence
raise Unauthorized() from e
if mailing_sudo.mailing_on_mailing_list:
return self._mailing_unsubscribe_from_list(mailing_sudo, document_id, email_found, hash_token_found)
return self._mailing_unsubscribe_from_document(mailing_sudo, document_id, email_found, hash_token_found)
def _mailing_unsubscribe_from_list(self, mailing, document_id, email, hash_token):
# Unsubscribe directly + Let the user choose their subscriptions
mailing.contact_list_ids._update_subscription_from_email(email, opt_out=True)
# compute name of unsubscribed list: hide non public lists
if all(not mlist.is_public for mlist in mailing.contact_list_ids):
lists_unsubscribed_name = _('You are no longer part of our mailing list(s).')
elif len(mailing.contact_list_ids) == 1:
lists_unsubscribed_name = _('You are no longer part of the %(mailing_name)s mailing list.',
mailing_name=mailing.contact_list_ids.name)
else:
lists_unsubscribed_name = _(
'You are no longer part of the %(mailing_names)s mailing list.',
mailing_names=', '.join(mlist.name for mlist in mailing.contact_list_ids if mlist.is_public)
)
return request.render(
'mass_mailing.page_mailing_unsubscribe',
dict(
self._prepare_mailing_subscription_values(
mailing, document_id, email, hash_token
),
last_action='subscription_updated',
unsubscribed_name=lists_unsubscribed_name,
)
)
def _mailing_unsubscribe_from_document(self, mailing, document_id, email, hash_token):
if document_id:
message = Markup(_(
'Blocklist request from unsubscribe link of mailing %(mailing_link)s (document %(record_link)s)',
**self._format_bl_request(mailing, document_id)
))
else:
message = Markup(_(
'Blocklist request from unsubscribe link of mailing %(mailing_link)s (direct link usage)',
**self._format_bl_request(mailing, document_id)
))
_blocklist_rec = request.env['mail.blacklist'].sudo()._add(email, message=Markup('<p>%s</p>') % message)
return request.render(
'mass_mailing.page_mailing_unsubscribe',
dict(
self._prepare_mailing_subscription_values(
mailing, document_id, email, hash_token
),
last_action='blocklist_add',
unsubscribed_name=_('You are no longer part of our services and will not be contacted again.'),
)
)
def _prepare_mailing_subscription_values(self, mailing, document_id, email, hash_token):
""" Prepare common values used in various subscription management or
blocklist flows done in portal. """
mail_blocklist = self._fetch_blocklist_record(email)
email_normalized = tools.email_normalize(email)
# fetch optout/blacklist reasons
opt_out_reasons = self._fetch_subscription_optouts()
# as there may be several contacts / email -> consider any opt-in overrides
# opt-out
contacts = self._fetch_contacts(email)
lists_optin = contacts.subscription_ids.filtered(
lambda sub: not sub.opt_out
).list_id.filtered('active')
lists_optout = contacts.subscription_ids.filtered(
lambda sub: sub.opt_out and sub.list_id not in lists_optin
).list_id.filtered('active')
lists_public = request.env['mailing.list'].sudo().search(
[('is_public', '=', True),
('id', 'not in', (lists_optin + lists_optout).ids)
],
limit=10,
order='create_date DESC, id DESC',
)
return {
# customer
'document_id': document_id,
'email': email,
'email_valid': bool(email_normalized),
'hash_token': hash_token,
'mailing_id': mailing.id,
'res_id': document_id,
# feedback
'feedback_enabled': True,
'feedback_readonly': False,
'opt_out_reasons': opt_out_reasons,
# blocklist
'blocklist_enabled': bool(
request.env['ir.config_parameter'].sudo().get_param(
'mass_mailing.show_blacklist_buttons',
default=True,
)
),
'blocklist_possible': mail_blocklist is not None,
'is_blocklisted': mail_blocklist.active if mail_blocklist else False,
# mailing lists
'contacts': contacts,
'lists_contacts': contacts.subscription_ids.list_id.filtered('active'),
'lists_optin': lists_optin,
'lists_optout': lists_optout,
'lists_public': lists_public,
}
@http.route('/mailing/list/update', type='json', auth='public', csrf=True)
def mailing_update_list_subscription(self, mailing_id=None, document_id=None,
email=None, hash_token=None,
lists_optin_ids=None, **post):
email_found, hash_token_found = self._fetch_user_information(email, hash_token)
try:
_mailing_sudo = self._check_mailing_email_token(
mailing_id, document_id, email_found, hash_token_found,
required_mailing_id=False
)
except BadRequest:
return 'error'
except (NotFound, Unauthorized):
return 'unauthorized'
contacts = self._fetch_contacts(email_found)
lists_optin = request.env['mailing.list'].sudo().browse(lists_optin_ids or []).exists()
# opt-out all not chosen lists
lists_to_optout = contacts.subscription_ids.filtered(
lambda sub: not sub.opt_out and sub.list_id not in lists_optin
).list_id
# opt-in in either already member, either public (to avoid trying to opt-in
# in private lists)
lists_to_optin = lists_optin.filtered(
lambda mlist: mlist.is_public or mlist in contacts.list_ids
)
lists_to_optout._update_subscription_from_email(email_found, opt_out=True)
lists_to_optin._update_subscription_from_email(email_found, opt_out=False)
return len(lists_to_optout)
@http.route('/mailing/feedback', type='json', auth='public', csrf=True)
def mailing_send_feedback(self, mailing_id=None, document_id=None,
email=None, hash_token=None,
last_action=None,
opt_out_reason_id=False, feedback=None,
**post):
""" Feedback can be given after some actions, notably after opt-outing
from mailing lists or adding an email in the blocklist.
This controller tries to write the customer feedback in the most relevant
record. Feedback consists in two parts, the opt-out reason (based on data
in 'mailing.subscription.optout' model) and the feedback itself (which
is triggered by the optout reason 'is_feedback' fields).
"""
email_found, hash_token_found = self._fetch_user_information(email, hash_token)
try:
mailing_sudo = self._check_mailing_email_token(
mailing_id, document_id, email_found, hash_token_found,
required_mailing_id=False,
)
except BadRequest:
return 'error'
except (NotFound, Unauthorized):
return 'unauthorized'
if not opt_out_reason_id:
return 'error'
feedback = feedback.strip() if feedback else ''
message = ''
if feedback:
if not request.env.user._is_public():
author_name = f'{request.env.user.name} ({email_found})'
else:
author_name = email_found
message = Markup("<p>%s<br />%s</p>") % (
_('Feedback from %(author_name)s', author_name=author_name),
feedback
)
# blocklist addition: opt-out and feedback linked to the mail.blacklist records
if last_action == 'blocklist_add':
mail_blocklist = self._fetch_blocklist_record(email)
if mail_blocklist:
if message:
mail_blocklist._track_set_log_message(message)
mail_blocklist.opt_out_reason_id = opt_out_reason_id
# opt-outed from mailing lists (either from a mailing or directly from 'my')
# -> in that case, update recently-updated subscription records and log on
# contacts
documents_for_post = []
if (last_action in {'subscription_updated', 'subscription_updated_optout'} or
(not last_action and (not mailing_sudo or mailing_sudo.mailing_on_mailing_list))):
contacts = self._fetch_contacts(email_found)
contacts.subscription_ids.filtered(
lambda sub: sub.opt_out and sub.opt_out_datetime >= (fields.Datetime.now() - timedelta(minutes=10))
).opt_out_reason_id = opt_out_reason_id
if message:
documents_for_post = contacts
# feedback coming from a mailing, without additional context information: log
elif mailing_sudo and message:
documents_for_post = request.env[mailing_sudo.mailing_model_real].sudo().search(
[('id', '=', document_id)
])
for document_sudo in documents_for_post:
document_sudo.message_post(body=message)
return True
@http.route(['/unsubscribe_from_list'], type='http', website=True, multilang=False, auth='public', sitemap=False)
def mailing_unsubscribe_placeholder_link(self, **post):
"""Dummy route so placeholder is not prefixed by language, MUST have multilang=False"""
return request.redirect('/mailing/my', code=301, local=True)
# ------------------------------------------------------------
# TRACKING
# ------------------------------------------------------------
@http.route('/mail/track/<int:mail_id>/<string:token>/blank.gif', type='http', auth='public')
def track_mail_open(self, mail_id, token, **post):
""" Email tracking. """
expected_token = request.env['mail.mail']._generate_mail_recipient_token(mail_id)
if not consteq(token, expected_token):
raise Unauthorized()
request.env['mailing.trace'].sudo().set_opened(domain=[('mail_mail_id_int', 'in', [mail_id])])
response = Response()
response.mimetype = 'image/gif'
response.data = base64.b64decode(b'R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==')
return response
@http.route('/r/<string:code>/m/<int:mailing_trace_id>', type='http', auth="public")
def full_url_redirect(self, code, mailing_trace_id, **post):
request.env['link.tracker.click'].sudo().add_click(
code,
ip=request.httprequest.remote_addr,
country_code=request.geoip.country_code,
mailing_trace_id=mailing_trace_id
)
redirect_url = request.env['link.tracker'].get_url_from_code(code)
if not redirect_url:
raise NotFound()
return request.redirect(redirect_url, code=301, local=False)
# ------------------------------------------------------------
# MAILING MANAGEMENT
# ------------------------------------------------------------
@http.route('/mailing/report/unsubscribe', type='http', website=True, auth='public')
def mailing_report_deactivate(self, token, user_id):
if not token or not user_id:
raise BadRequest()
user = request.env['res.users'].sudo().browse(int(user_id)).exists()
if not user or not user.has_group('mass_mailing.group_mass_mailing_user') or \
not consteq(token, request.env['mailing.mailing']._generate_mailing_report_token(user.id)):
raise Unauthorized()
request.env['ir.config_parameter'].sudo().set_param('mass_mailing.mass_mailing_reports', False)
render_vals = {}
if user.has_group('base.group_system'):
render_vals = {'menu_id': request.env.ref('mass_mailing.menu_mass_mailing_global_settings').id}
return request.render('mass_mailing.mailing_report_deactivated', render_vals)
@http.route(['/mailing/<int:mailing_id>/view'], type='http', website=True, auth='public')
def mailing_view_in_browser(self, mailing_id, email=None, document_id=None, hash_token=None, **kwargs):
# backward compatibility: temporary for mailings sent before migation to 17
document_id = document_id or kwargs.get('res_id')
hash_token = hash_token or kwargs.get('token')
try:
mailing_sudo = self._check_mailing_email_token(
mailing_id, document_id, email, hash_token,
required_mailing_id=True,
)
except NotFound as e:
raise Unauthorized() from e
# do not force lang, will simply use user context
document_id = int(document_id) if document_id and document_id.isdigit() else 0
html_markupsafe = mailing_sudo._render_field(
'body_html',
[document_id],
compute_lang=False,
options={'post_process': False}
)[document_id]
# Update generic URLs (without parameters) to final ones
if document_id:
html_markupsafe = html_markupsafe.replace(
'/unsubscribe_from_list',
mailing_sudo._get_unsubscribe_url(email, document_id)
)
else: # when manually trying a /view on a mailing, not through email link
html_markupsafe = html_markupsafe.replace(
'/unsubscribe_from_list',
werkzeug.urls.url_join(
mailing_sudo.get_base_url(),
f'/mailing/{mailing_sudo.id}/unsubscribe',
)
)
return request.render(
'mass_mailing.mailing_view',
{
'body': html_markupsafe,
},
)
# ------------------------------------------------------------
# BLACKLIST
# ------------------------------------------------------------
@http.route('/mailing/blocklist/add', type='json', auth='public')
def mail_blocklist_add(self, mailing_id=None, document_id=None,
email=None, hash_token=None):
email_found, hash_token_found = self._fetch_user_information(email, hash_token)
try:
mailing_sudo = self._check_mailing_email_token(
mailing_id, document_id, email_found, hash_token_found,
required_mailing_id=False,
)
except BadRequest:
return 'error'
except (NotFound, Unauthorized):
return 'unauthorized'
if mailing_sudo:
message = Markup(
_(
'Blocklist request from portal of mailing %(mailing_link)s (document %(record_link)s)',
**self._format_bl_request(mailing_sudo, document_id)
)
)
else:
message = Markup('<p>%s</p>') % _('Blocklist request from portal')
_blocklist_rec = request.env['mail.blacklist'].sudo()._add(email_found, message=message)
return True
@http.route('/mailing/blocklist/remove', type='json', auth='public')
def mail_blocklist_remove(self, mailing_id=None, document_id=None,
email=None, hash_token=None):
email_found, hash_token_found = self._fetch_user_information(email, hash_token)
try:
mailing_sudo = self._check_mailing_email_token(
mailing_id, document_id, email_found, hash_token_found,
required_mailing_id=False,
)
except BadRequest:
return 'error'
except (NotFound, Unauthorized):
return 'unauthorized'
if mailing_sudo and document_id:
message = Markup(
_(
'Blocklist removal request from portal of mailing %(mailing_link)s (document %(record_link)s)',
**self._format_bl_request(mailing_sudo, document_id)
)
)
else:
message = Markup('<p>%s</p>') % _('Blocklist removal request from portal')
_blocklist_rec = request.env['mail.blacklist'].sudo()._remove(email_found, message=message)
return True
def _format_bl_request(self, mailing, document_id):
mailing_model_name = request.env['ir.model']._get(mailing.mailing_model_real).display_name
return {
'mailing_link': Markup(f'<a href="#" data-oe-model="mailing.mailing" data-oe-id="{mailing.id}">{escape(mailing.subject)}</a>'),
'record_link': Markup(
f'<a href="#" data-oe-model="{escape(mailing.mailing_model_real)}" data-oe-id="{int(document_id)}">{escape(mailing_model_name)}</a>'
) if document_id else '',
}
# ------------------------------------------------------------
# PREVIEW
# ------------------------------------------------------------
@http.route('/mailing/mobile/preview', methods=['GET'], type='http', auth='user', website=True)
def mass_mailing_preview_mobile_content(self):
return request.render("mass_mailing.preview_content_mobile", {})

16
data/digest_data.xml Normal file
View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<template id="digest_mail_main" inherit_id="digest.digest_mail_main">
<xpath expr="//div[hasclass('by_odoo')]/t[last()]" position="after">
<t t-if="mailing_report_token">
<a t-attf-href="/mailing/report/unsubscribe?token=#{mailing_report_token}&amp;user_id=#{user_id}"
target="_blank" style="text-decoration: none;">
<span style="color: #8f8f8f;">Turn off Mailing Reports</span>
</a>
</t>
</xpath>
</template>
</data>
</odoo>

162
data/ir_attachment_data.xml Normal file
View File

@ -0,0 +1,162 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Snippets' Default Images -->
<record id="mass_mailing.s_cover_default_image" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_cover_default_image.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_cover.jpg</field>
</record>
<record id="mass_mailing.s_media_list_default_image_1" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_media_list_default_image_1.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_media_list_1.jpg</field>
</record>
<record id="mass_mailing.s_media_list_default_image_2" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_media_list_default_image_2.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_media_list_2.jpg</field>
</record>
<record id="mass_mailing.s_media_list_default_image_3" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_media_list_default_image_3.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_media_list_3.jpg</field>
</record>
<record id="mass_mailing.s_company_team_default_image_1" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_company_team_default_image_1.png</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_team_member_1.png</field>
</record>
<record id="mass_mailing.s_company_team_default_image_2" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_company_team_default_image_2.png</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_team_member_2.png</field>
</record>
<record id="mass_mailing.s_company_team_default_image_3" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_company_team_default_image_3.png</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_team_member_3.png</field>
</record>
<record id="mass_mailing.s_company_team_default_image_4" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_company_team_default_image_4.png</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_team_member_4.png</field>
</record>
<record id="mass_mailing.s_reference_default_image_1" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_reference_default_image_1.png</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_references_1.png</field>
</record>
<record id="mass_mailing.s_reference_default_image_2" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_reference_default_image_2.png</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_references_2.png</field>
</record>
<record id="mass_mailing.s_reference_default_image_3" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_reference_default_image_3.png</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_references_3.png</field>
</record>
<record id="mass_mailing.s_reference_default_image_4" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_reference_default_image_4.png</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_references_4.png</field>
</record>
<record id="mass_mailing.s_product_list_default_image_1" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_product_list_default_image_1.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_product_1.jpg</field>
</record>
<record id="mass_mailing.s_product_list_default_image_2" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_product_list_default_image_2.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_product_2.jpg</field>
</record>
<record id="mass_mailing.s_product_list_default_image_3" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_product_list_default_image_3.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_product_3.jpg</field>
</record>
<record id="mass_mailing.s_blockquote_default_image" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_blockquote_default_image.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_team_member_2.png</field>
</record>
<record id="mass_mailing.s_image_text_default_image" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_image_text_default_image.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_image_text.jpg</field>
</record>
<record id="mass_mailing.s_event_default_image_1" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_event_default_image_1.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_event_1.jpg</field>
</record>
<record id="mass_mailing.s_event_default_image_2" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_event_default_image_2.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_event_2.jpg</field>
</record>
<record id="mass_mailing.s_masonry_block_default_image_1" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_masonry_block_default_image_1.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_masonry_block_1.jpg</field>
</record>
<record id="mass_mailing.s_masonry_block_default_image_2" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_masonry_block_default_image_2.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_masonry_block_2.jpg</field>
</record>
<record id="mass_mailing.s_picture_default_image" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_picture_default_image.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_picture.jpg</field>
</record>
<record id="mass_mailing.s_text_image_default_image" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_text_image_default_image.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_text_image.jpg</field>
</record>
<record id="mass_mailing.s_three_columns_default_image_1" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_three_columns_default_image_1.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_three_cols_1.jpg</field>
</record>
<record id="mass_mailing.s_three_columns_default_image_2" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_three_columns_default_image_2.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_three_cols_2.jpg</field>
</record>
<record id="mass_mailing.s_three_columns_default_image_3" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">s_three_columns_default_image_3.jpg</field>
<field name="type">url</field>
<field name="url">/mass_mailing/static/src/img/theme_default/s_default_image_three_cols_3.jpg</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Enable Mass Mailing Reports -->
<function model="ir.config_parameter"
name="set_param"
eval="('mass_mailing.mass_mailing_reports', 'True')"/>
<!-- Display blacklist button to customers on portal page -->
<function model="ir.config_parameter"
name="set_param"
eval="('mass_mailing.show_blacklist_buttons', True)"/>
</data>
</odoo>

30
data/ir_cron_data.xml Normal file
View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Cron that processes the mass mailing queue -->
<record id="ir_cron_mass_mailing_queue" model="ir.cron">
<field name="name">Mail Marketing: Process queue</field>
<field name="model_id" ref="model_mailing_mailing"/>
<field name="state">code</field>
<field name="code">model._process_mass_mailing_queue()</field>
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field eval="False" name="doall" />
</record>
<!-- Cron that processes the a/b testing -->
<record id="ir_cron_mass_mailing_ab_testing" model="ir.cron">
<field name="name">Mail Marketing: A/B Testing</field>
<field name="model_id" ref="model_utm_campaign"/>
<field name="state">code</field>
<field name="code">model._cron_process_mass_mailing_ab_testing()</field>
<field name="user_id" ref="base.user_root"/>
<field name="active" eval="False"/>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="True"/>
</record>
</data>
</odoo>

10
data/ir_module_data.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<record model="ir.module.category" id="base.module_category_marketing_email_marketing">
<field name="sequence">19</field>
<field name="description">Helps you manage your mass mailing to design
professional emails and reuse templates.</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!--
Reference: https://litmus.com/community/learning/24-how-to-code-a-responsive-email-from-scratch
https://www.campaignmonitor.com/css/link-element/link-in-head/
-->
<template id="mass_mailing_mail_layout">
&lt;!DOCTYPE html&gt;
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="format-detection" content="telephone=no"/>
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=no;"/>
<meta http-equiv="X-UA-Compatible" content="IE=9; IE=8; IE=7; IE=EDGE" />
<t t-out="mailing_style"/>
<!--
Prevent Outlook from distorting images with DPI scaling (see
https://www.htmlemailcheck.com/knowledge-base/dpi-scaling-in-outlook-2007-2013/):
-->
<!--[if mso]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
</head>
<body>
<t t-out="body"/>
</body>
</html>
</template>
</data>
<data noupdate="0">
<template id="ab_testing_description" name="Mass Mailing: A/B Test Description">
<div name="ab_testing_description" class="mb-2">
<t t-if="mailing.state == 'done' and mailing.sent == 0 and mailing.failed == 0 and mailing.canceled == 0">
<span>There wasn't enough recipients left for this mailing</span>
</t>
<t t-if="mailing.ab_testing_completed">
<p t-if="mailing.ab_testing_is_winner_mailing">
This <t t-out="mailing.mailing_type_description"/> is the winner of the A/B testing campaign and has been sent to all remaining recipients.
</p>
<p t-else="">The winner has already been sent. Use <b>Compare Version</b> to get an overview of this A/B testing campaign.</p>
</t>
<t t-elif="ab_testing_count >= 2">
<p>
A sample of <b><t t-out="mailing.ab_testing_pc"/>% of recipients</b> will receive this version.<br/>
<t t-if="total_ab_testing_pc > 100 and mailing.active">
<i class="fa fa-exclamation-triangle text-danger" aria-hidden="true"/>
The sum of all percentages for this A/B campaign totals <b class="text-danger"><t t-out="total_ab_testing_pc"/>%</b> of all potential recipients.<br/>
<b class="text-danger">Some of the mailings will not be sent</b>, as only 1 email will be sent for each unique recipient in this campaign.
</t>
</p>
<p>
<t t-if="mailing.ab_testing_winner_selection == 'manual'">Don't forget to send your preferred version</t>
<t t-elif="not mailing.ab_testing_schedule_datetime">Since the date and time for this test has not been scheduled, don't forget to manually send your preferred version.</t>
<t t-else="">
Then on <b><t t-out="mailing.ab_testing_schedule_datetime.strftime('%b %d, %Y')"/></b> the <t t-out="mailing.mailing_type_description"/> having the <b><t t-out="ab_testing_winner_selection_description"/></b> will be sent
</t> to the remaining recipients.
</p>
</t>
<t t-else="">
<p>
A sample of <b><t t-out="mailing.ab_testing_pc"/>% of recipients</b> will receive this version.
</p>
<t t-if="mailing.ab_testing_winner_selection != 'manual'">
<p>Try different variations in the campaign to compare their <t t-out="ab_testing_winner_selection_description"/>.</p>
<p>Once the best version is identified, we will send the best one to the remaining recipients.</p>
</t>
<p t-else="">Use alternative versions to be able to select the winner.</p>
</t>
</div>
</template>
<template id="mass_mailing.mass_mailing_kpi_link_trackers" name="Marketing: mailing link trackers statistic">
<table t-if="link_trackers" class="global_table_layout" bgcolor="#ffffff" cellspacing="0" cellpadding="0" align="center" border="0" style="width: 100%;">
<tr>
<td style="width: 100%;">
<table cellspacing="0" cellpadding="0" border="0" align="center" style="width:100%;">
<tr>
<td align="left">
<span style="color:#282f33; font-size: 15px; font-weight: bold; line-height: 30px">
<t t-esc="'Click Rate Report on %i %s Sent' % (object.expected, mailing_type)"/>
</span>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td style="margin: 0; padding:0;">
<table cellspacing="0" cellpadding="0" border="0" align="center" style="width:100%; border-collapse: collapse;">
<tr t-att-style="'color: ' + (company.email_secondary_color or '#875a7b') + '; font-size: 16px; font-weight: 500;'">
<td style="width: 70%;padding: 10px 0; text-align: center; border: 1px solid #e7e7e7;">Button Label</td>
<td style="width: 30%;padding: 10px 0; text-align: center; border: 1px solid #e7e7e7;">%Click (Total)</td>
</tr>
<tr t-foreach="link_trackers" t-as="link_tracker" style="color: #888888; font-size: 15px; font-weight: 300;">
<td style="width: 70%;padding: 10px 0; border: 1px solid #e7e7e7;">
<a t-att-href="link_tracker.absolute_url" target="_blank" style="color: #56b3b5; text-decoration: none;" t-esc="link_tracker.label or link_tracker.url"/>
</td>
<td style="width: 30%;padding: 10px 0; text-align: center; border: 1px solid #e7e7e7;">
<t t-esc="int(link_tracker.count * 100 / object.sent) if object.sent else 0"/>% (<t t-esc="link_tracker.count"/>)
</td>
</tr>
</table>
</td>
</tr>
</table>
</template>
</data>
</odoo>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="mailing_list_data" model="mailing.list">
<field name="name">Newsletter</field>
<field name="is_public">True</field>
</record>
<record id="mass_mail_contact_0" model="mailing.contact">
<field name="name" model="res.users" eval="obj().env.ref('base.user_admin').name"/>
<field name="email" model="res.users" eval="obj().env.ref('base.user_admin').email"/>
<field name="list_ids" eval="[(5, 0, 0)]"/>
</record>
</data>
</odoo>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="mailing_list_data_sub_contact_0" model="mailing.subscription">
<field name="contact_id" ref="mass_mailing.mass_mail_contact_0"/>
<field name="list_id" ref="mass_mailing.mailing_list_data"/>
</record>
</data>
</odoo>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo><data noupdate="0">
<record id="mailing_subscription_optout_data_0" model="mailing.subscription.optout">
<field name="name">I never subscribed to this list</field>
<field name="sequence">1</field>
</record>
<record id="mailing_subscription_optout_data_1" model="mailing.subscription.optout">
<field name="name">I changed my mind</field>
<field name="sequence">2</field>
</record>
<record id="mailing_subscription_optout_data_2" model="mailing.subscription.optout">
<field name="name">I receive too many emails from this list</field>
<field name="sequence">3</field>
</record>
<record id="mailing_subscription_optout_data_3" model="mailing.subscription.optout">
<field name="name">The content of these emails is not relevant to me</field>
<field name="sequence">4</field>
</record>
<record id="mailing_subscription_optout_data_4" model="mailing.subscription.optout">
<field name="name">Other</field>
<field name="sequence">99</field>
<field name="is_feedback" eval="True"/>
</record>
</data></odoo>

8
data/res_users_data.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="base.default_user" model="res.users">
<field name="groups_id" eval="[(4,ref('mass_mailing.group_mass_mailing_user'))]"/>
</record>
</data>
</odoo>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Create Extra Mailing List for Demo -->
<record id="mailing_list_1" model="mailing.list">
<field name="name">Imported Contacts</field>
<field name="is_public">False</field>
</record>
<!-- Create Contacts -->
<record id="mass_mail_contact_1" model="mailing.contact">
<field name="name">Aristide Antario</field>
<field name="email">alexandre.antario@example.com</field>
<field name="list_ids" eval="[(5, 0, 0)]"/>
</record>
<record id="mass_mail_contact_2" model="mailing.contact">
<field name="name">Beverly Bridge</field>
<field name="email">beverly.bridge@example.com</field>
<field name="list_ids" eval="[(5, 0, 0)]"/>
</record>
<record id="mass_mail_contact_3" model="mailing.contact">
<field name="name">Carol Cartridge</field>
<field name="email">carol.cartridge@example.com</field>
<field name="list_ids" eval="[(5, 0, 0)]"/>
</record>
<record id="mass_mail_contact_4" model="mailing.contact">
<field name="name">David Dawson</field>
<field name="email">david.dawson@example.com</field>
<field name="list_ids" eval="[(5, 0, 0)]"/>
</record>
<record id="mass_mail_contact_5" model="mailing.contact">
<field name="name">Elsa Ericson</field>
<field name="email">elsa.ericson@example.com</field>
<field name="message_bounce">5</field>
<field name="list_ids" eval="[(5, 0, 0)]"/>
</record>
<record id="mass_mail_contact_6" model="mailing.contact">
<field name="name">Franz Faubourg</field>
<field name="email">franz.faubourg@example.com</field>
<field name="list_ids" eval="[(5, 0, 0)]"/>
</record>
</data>
</odoo>

102
demo/mailing_mailing.xml Normal file
View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="mass_mail_attach_1" model="ir.attachment">
<field name="datas">bWlncmF0aW9uIHRlc3Q=</field>
<field name="name">SampleDoc.doc</field>
</record>
<record id="mass_mail_1" model="mailing.mailing">
<field name="name">Newsletter 1</field>
<field name="subject">Monthly Newsletter</field>
<field name="state">done</field>
<field name="user_id" ref="base.user_admin"/>
<field name="email_from">info@yourcompany.example.com</field>
<field name="sent_date" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="campaign_id" ref="mass_mail_campaign_1"/>
<field name="source_id" ref="mass_mailing.utm_source_0"/>
<field name="mailing_model_id" ref="base.model_res_partner"/>
<field name="mailing_domain" eval="[('parent_id', '=', ref('base.res_partner_4'))]"/>
<field name="reply_to_mode">new</field>
<field name="reply_to">Info &lt;info@yourcompany.example.com&gt;</field>
<field name="body_arch" type="html">
<div class="o_layout o_default_theme oe_unremovable oe_unmovable" data-name="Mailing">
<div class="container o_mail_wrapper oe_unremovable oe_unmovable" style="border-collapse:collapse;">
<div class="row">
<div class="col o_mail_no_options o_mail_wrapper_td bg-white oe_structure o_editable" style="text-align:left;width:100%;">
<div class="o_mail_block_header_logo" data-snippet="s_mail_block_header_logo">
<div class="o_mail_snippet_general" style="margin:0px auto 0px auto;background-color:rgb(255, 255, 255);max-width:600px;width:100%;">
<div class="container o_mail_h_padding" style="padding:0 20px 0 20px;width:100%;border-collapse:separate;">
<div class="row">
<div valign="center" width="30%" class="col text-center o_mail_v_padding pb0" style="padding:20px 0 0px 0;vertical-align:middle;text-align:center;">
<a href="http://www.example.com" style="text-decoration:none;font-weight:bold;background-color:transparent;color:rgb(100, 89, 116);">
<img border="0" src="/mass_mailing/static/src/img/theme_default/s_default_image_header_logo.png" style="border-style:none;height:auto;vertical-align:middle;max-width:400px;width:auto"/>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="o_mail_block_footer_separator" data-snippet="s_hr" style="margin:0 20px 0 20px;">
<div class="o_mail_snippet_general" style="margin:0px auto 0px auto;background-color:rgb(255, 255, 255);max-width:600px;width:100%;">
<div class="container" style="width:100%;border-collapse:separate;">
<div class="row">
<div valign="top" style="padding:20px 0 20px 0;text-align:left;vertical-align:top;width:100%;" class="col o_mail_v_padding o_mail_no_colorpicker">
<div style="background-color:rgb(245, 245, 245);height:2px;width:100%;" class="separator"></div>
</div>
</div>
</div>
</div>
</div>
<div class="o_mail_block_text" data-snippet="s_text_block">
<div class="o_mail_snippet_general" style="margin:0px auto 0px auto;background-color:rgb(255, 255, 255);max-width:600px;width:100%;">
<div class="container" style="width:100%;border-collapse:separate;">
<div class="row">
<div class="col-12 o_mail_h_padding o_mail_v_padding o_mail_no_colorpicker" style="padding:20px;text-align:left;vertical-align:top;">
<p style="margin:0px 0 1rem 0;font-size:14px;">
Great stories have personality. Consider telling a great story that provides personality. Writing a story with personality for potential clients will assist with making a relationship connection. This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience.
<br/>Great stories are for everyone even when only written for just one person. If you try to write with a wide general audience in mind, your story will ring false and be bland. No one will be interested. Write for one person. If its genuine for the one, its genuine for the rest.
</p>
</div>
</div>
</div>
</div>
</div>
<div class="o_mail_block_footer_social o_mail_footer_social_center" data-snippet="s_mail_block_footer_social">
<div class="o_mail_snippet_general" style="margin:0px auto 0px auto;background-color:rgb(255, 255, 255);max-width:600px;width:100%;">
<div align="center" class="container" style="border-style:solid none none none;padding:20px 0 20px 0;border-top-color:rgb(245, 245, 245);border-top-width:2px;width:100%;border-collapse:separate;">
<div class="row">
<div class="col o_mail_footer_links o_default_snippet_text" style="padding:10px 0 10px 0;text-align:center;vertical-align:middle;">
<a href="/unsubscribe_from_list" class="btn btn-link o_default_snippet_text" style="text-decoration:none;border-radius:0.25rem;border-style:solid;padding:0px;cursor:pointer;line-height:1.5;font-size:12px;border-start-color:transparent;border-bottom-color:transparent;border-end-color:transparent;border-top-color:transparent;border-start-width:1px;border-bottom-width:1px;border-end-width:1px;border-top-width:1px;user-select:none;vertical-align:middle;white-space:nowrap;text-align:center;font-weight:bold;display:inline-block;background-color:transparent;color:rgb(100, 89, 116);">Unsubscribe</a> |
<a href="/contactus" class="btn btn-link o_default_snippet_text" style="text-decoration:none;border-radius:0.25rem;border-style:solid;padding:0px;cursor:pointer;line-height:1.5;font-size:12px;border-start-color:transparent;border-bottom-color:transparent;border-end-color:transparent;border-top-color:transparent;border-start-width:1px;border-bottom-width:1px;border-end-width:1px;border-top-width:1px;user-select:none;vertical-align:middle;white-space:nowrap;text-align:center;font-weight:bold;display:inline-block;background-color:transparent;color:rgb(100, 89, 116);">Contact</a>
</div>
</div>
<div class="row">
<div class="col" style="text-align:left;vertical-align:middle;">
<p class="o_mail_footer_copy" style="margin:0px 0 1rem 0;text-align:center;font-weight:bold;color:rgb(147, 146, 146);font-size:9px;">
<img src="/web_editor/font_to_img/61945/rgb(147,146,146)/9" data-class="fa fa-copyright" style="border-style:none;max-width:100%;width:100%;vertical-align:middle;height: auto; width: auto;"/>2018 All Rights Reserved
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div align="center" class="container" style="width:100%;border-collapse:separate;">
<div class="row">
<div align="center" style="padding:16px 0 16px 0;" class="col pt16 pb16">
Powered by <a target="_blank" href="https://www.odoo.com" style="text-decoration:none;background-color:transparent;color:#875A7B;">Odoo</a>
</div>
</div>
</div>
</div>
</field>
<field name="attachment_ids" eval="[(4, ref('mass_mail_attach_1'))]"/>
</record>
<!-- Generate link tracker information from it -->
<function model="mailing.mailing" name="convert_links" eval="[ref('mass_mailing.mass_mail_1')]"/>
</data>
</odoo>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Subscription to 'mailing_list_data' -->
<record id="mailing_list_data_sub_contact_1" model="mailing.subscription">
<field name="contact_id" ref="mass_mailing.mass_mail_contact_1"/>
<field name="list_id" ref="mass_mailing.mailing_list_data"/>
</record>
<record id="mailing_list_data_sub_contact_2" model="mailing.subscription">
<field name="contact_id" ref="mass_mailing.mass_mail_contact_2"/>
<field name="list_id" ref="mass_mailing.mailing_list_data"/>
</record>
<record id="mailing_list_data_sub_contact_3" model="mailing.subscription">
<field name="contact_id" ref="mass_mailing.mass_mail_contact_3"/>
<field name="list_id" ref="mass_mailing.mailing_list_data"/>
</record>
<record id="mailing_list_data_sub_contact_4" model="mailing.subscription">
<field name="contact_id" ref="mass_mailing.mass_mail_contact_4"/>
<field name="list_id" ref="mass_mailing.mailing_list_data"/>
<field name="opt_out">True</field>
</record>
<record id="mailing_list_data_sub_contact_5" model="mailing.subscription">
<field name="contact_id" ref="mass_mailing.mass_mail_contact_5"/>
<field name="list_id" ref="mass_mailing.mailing_list_data"/>
</record>
<record id="mailing_list_data_sub_contact_6" model="mailing.subscription">
<field name="contact_id" ref="mass_mailing.mass_mail_contact_6"/>
<field name="list_id" ref="mass_mailing.mailing_list_data"/>
<field name="opt_out">True</field>
</record>
<!-- Subscription to 'mailing_list_1' -->
<record id="mailing_list_1_sub_contact_3" model="mailing.subscription">
<field name="contact_id" ref="mass_mailing.mass_mail_contact_3"/>
<field name="list_id" ref="mass_mailing.mailing_list_1"/>
</record>
<record id="mailing_list_1_sub_contact_6" model="mailing.subscription">
<field name="contact_id" ref="mass_mailing.mass_mail_contact_6"/>
<field name="list_id" ref="mass_mailing.mailing_list_1"/>
</record>
<!-- Create Blacklist Records -->
<record id="mail_blacklist_demo_1" model="mail.blacklist">
<field name="email">elsa.ericson@example.com</field>
</record>
</data>
</odoo>

132
demo/mailing_trace.xml Normal file
View File

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="mass_mail_1_stat_0" model="mailing.trace">
<field name="mass_mailing_id" ref="mass_mail_1"/>
<field name="message_id">1111000@odoo.com</field>
<field name="model">res.partner</field>
<field name="res_id" ref="base.res_partner_address_7"/>
<field name="email">billy.fox45@example.com</field>
<field name="trace_status">reply</field>
<field name="sent_datetime" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="open_datetime" eval="(DateTime.today() - relativedelta(days=4)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="reply_datetime" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="write_date" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
<record id="mass_mail_1_stat_1" model="mailing.trace">
<field name="mass_mailing_id" ref="mass_mail_1"/>
<field name="message_id">1111001@odoo.com</field>
<field name="model">res.partner</field>
<field name="res_id" ref="base.res_partner_address_13"/>
<field name="email">kim.snyder96@example.com</field>
<field name="trace_status">reply</field>
<field name="sent_datetime" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="open_datetime" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="reply_datetime" eval="(DateTime.today() - relativedelta(days=0)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="write_date" eval="(DateTime.today() - relativedelta(days=0)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
<record id="mass_mail_1_stat_2" model="mailing.trace">
<field name="mass_mailing_id" ref="mass_mail_1"/>
<field name="message_id">1111002@odoo.com</field>
<field name="model">res.partner</field>
<field name="res_id" ref="base.res_partner_address_14"/>
<field name="email">edith.sanchez68@example.com</field>
<field name="trace_status">open</field>
<field name="sent_datetime" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="open_datetime" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="write_date" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
<record id="mass_mail_1_stat_3" model="mailing.trace">
<field name="mass_mailing_id" ref="mass_mail_1"/>
<field name="message_id">1111003@odoo.com</field>
<field name="model">res.partner</field>
<field name="res_id" ref="base.res_partner_address_24"/>
<field name="email">theodore.gardner36@example.com</field>
<field name="trace_status">open</field>
<field name="sent_datetime" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="open_datetime" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="write_date" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
<record id="mass_mail_1_stat_4" model="mailing.trace">
<field name="mass_mailing_id" ref="mass_mail_1"/>
<field name="message_id">1111004@odoo.com</field>
<field name="model">res.partner</field>
<field name="res_id" ref="base.res_partner_address_32"/>
<field name="email">sandra.neal80@example.com</field>
<field name="trace_status">sent</field>
<field name="sent_datetime" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="write_date" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
<record id="mass_mail_1_stat_5" model="mailing.trace">
<field name="mass_mailing_id" ref="mass_mail_1"/>
<field name="message_id">1111005@odoo.com</field>
<field name="model">res.partner</field>
<field name="res_id" ref="base.res_partner_address_33"/>
<field name="email">julie.richards84@example.com</field>
<field name="trace_status">error</field>
<field name="sent_datetime" eval="False"/>
</record>
<record id="mass_mail_1_stat_6" model="mailing.trace">
<field name="mass_mailing_id" ref="mass_mail_1"/>
<field name="message_id">1111006@odoo.com</field>
<field name="model">res.partner</field>
<field name="res_id" ref="base.res_partner_address_34"/>
<field name="email">travis.mendoza24@example.com</field>
<field name="trace_status">bounce</field>
<field name="sent_datetime" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="write_date" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
</record>
<record id="mass_mail_1_stat_7" model="mailing.trace">
<field name="mass_mailing_id" ref="mass_mail_1"/>
<field name="message_id">1111007@odoo.com</field>
<field name="model">res.partner</field>
<field name="res_id" ref="base.res_partner_address_34"/>
<field name="email">travis.mendoza24@example.com</field>
<field name="trace_status">bounce</field>
<field name="sent_datetime" eval="False"/>
</record>
<!-- Generate some clicks -->
<function model="link.tracker.click" name="add_click">
<value model="link.tracker.code"
search="[('link_id.url', '=', 'http://www.example.com')]"
use="code"/>
<value name="ip">100.01.02.03</value>
<value name="country_code">BE</value>
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_0')"/>
</function>
<function model="link.tracker.click" name="add_click">
<value model="link.tracker.code"
search="[('link_id.url', '=', 'http://www.example.net/page/contactus')]"
use="code"/>
<value name="ip">100.01.02.03</value>
<value name="country_code">BE</value>
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_0')"/>
</function>
<function model="link.tracker.click" name="add_click">
<value model="link.tracker.code"
search="[('link_id.url', '=', 'http://www.example.com')]"
use="code"/>
<value name="ip">100.01.02.04</value>
<value name="country_code">BE</value>
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_1')"/>
</function>
<function model="link.tracker.click" name="add_click">
<value model="link.tracker.code"
search="[('link_id.url', '=', 'http://www.example.net/page/contactus')]"
use="code"/>
<value name="ip">100.01.02.04</value>
<value name="country_code">BE</value>
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_0')"/>
</function>
<function model="link.tracker.click" name="add_click">
<value model="link.tracker.code"
search="[('link_id.url', '=', 'http://www.example.com')]"
use="code"/>
<value name="ip">100.01.02.05</value>
<value name="country_code">BE</value>
<value name="mailing_trace_id" eval="ref('mass_mail_1_stat_2')"/>
</function>
</data>
</odoo>

15
demo/utm.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Create campaign and mailings -->
<record id="utm_source_0" model="utm.source">
<field name="name">Newsletter 1</field>
</record>
<record id="mass_mail_campaign_1" model="utm.campaign">
<field name="name">Newsletter</field>
<field name="stage_id" ref="utm.campaign_stage_1"/>
<field name="user_id" ref="base.user_admin"/>
<field name="tag_ids" eval="[(6,0,[ref('utm.utm_tag_1')])]"/>
</record>
</data>
</odoo>

9
doc/changelog.rst Normal file
View File

@ -0,0 +1,9 @@
.. _changelog:
Changelog
=========
`trunk (saas-2)`
----------------
- added module

13
doc/index.rst Normal file
View File

@ -0,0 +1,13 @@
Mass Mailing module documentation
=================================
Mass Mailing documentation topics
'''''''''''''''''''''''''''''''''
Changelog
'''''''''
.. toctree::
:maxdepth: 1
changelog.rst

4762
i18n/af.po Normal file

File diff suppressed because it is too large Load Diff

4758
i18n/am.po Normal file

File diff suppressed because it is too large Load Diff

5465
i18n/ar.po Normal file

File diff suppressed because it is too large Load Diff

4769
i18n/az.po Normal file

File diff suppressed because it is too large Load Diff

5260
i18n/bg.po Normal file

File diff suppressed because it is too large Load Diff

4763
i18n/bs.po Normal file

File diff suppressed because it is too large Load Diff

5478
i18n/ca.po Normal file

File diff suppressed because it is too large Load Diff

5363
i18n/cs.po Normal file

File diff suppressed because it is too large Load Diff

5354
i18n/da.po Normal file

File diff suppressed because it is too large Load Diff

5536
i18n/de.po Normal file

File diff suppressed because it is too large Load Diff

4764
i18n/el.po Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/en_GB.po Normal file

File diff suppressed because it is too large Load Diff

5513
i18n/es.po Normal file

File diff suppressed because it is too large Load Diff

5514
i18n/es_419.po Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/es_BO.po Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/es_CL.po Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/es_CO.po Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/es_CR.po Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/es_DO.po Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/es_EC.po Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/es_PE.po Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/es_PY.po Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/es_VE.po Normal file

File diff suppressed because it is too large Load Diff

5444
i18n/et.po Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/eu.po Normal file

File diff suppressed because it is too large Load Diff

5261
i18n/fa.po Normal file

File diff suppressed because it is too large Load Diff

5531
i18n/fi.po Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/fo.po Normal file

File diff suppressed because it is too large Load Diff

5530
i18n/fr.po Normal file

File diff suppressed because it is too large Load Diff

4760
i18n/fr_BE.po Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/fr_CA.po Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/gl.po Normal file

File diff suppressed because it is too large Load Diff

4766
i18n/gu.po Normal file

File diff suppressed because it is too large Load Diff

5332
i18n/he.po Normal file

File diff suppressed because it is too large Load Diff

4781
i18n/hr.po Normal file

File diff suppressed because it is too large Load Diff

5263
i18n/hu.po Normal file

File diff suppressed because it is too large Load Diff

5495
i18n/id.po Normal file

File diff suppressed because it is too large Load Diff

4762
i18n/is.po Normal file

File diff suppressed because it is too large Load Diff

5514
i18n/it.po Normal file

File diff suppressed because it is too large Load Diff

5345
i18n/ja.po Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/ka.po Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/kab.po Normal file

File diff suppressed because it is too large Load Diff

4765
i18n/km.po Normal file

File diff suppressed because it is too large Load Diff

5368
i18n/ko.po Normal file

File diff suppressed because it is too large Load Diff

4762
i18n/lb.po Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/lo.po Normal file

File diff suppressed because it is too large Load Diff

5282
i18n/lt.po Normal file

File diff suppressed because it is too large Load Diff

5251
i18n/lv.po Normal file

File diff suppressed because it is too large Load Diff

5227
i18n/mass_mailing.pot Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/mk.po Normal file

File diff suppressed because it is too large Load Diff

4783
i18n/mn.po Normal file

File diff suppressed because it is too large Load Diff

4772
i18n/nb.po Normal file

File diff suppressed because it is too large Load Diff

4758
i18n/ne.po Normal file

File diff suppressed because it is too large Load Diff

5520
i18n/nl.po Normal file

File diff suppressed because it is too large Load Diff

5476
i18n/pl.po Normal file

File diff suppressed because it is too large Load Diff

5239
i18n/pt.po Normal file

File diff suppressed because it is too large Load Diff

5517
i18n/pt_BR.po Normal file

File diff suppressed because it is too large Load Diff

4811
i18n/ro.po Normal file

File diff suppressed because it is too large Load Diff

5525
i18n/ru.po Normal file

File diff suppressed because it is too large Load Diff

5288
i18n/sk.po Normal file

File diff suppressed because it is too large Load Diff

5295
i18n/sl.po Normal file

File diff suppressed because it is too large Load Diff

4761
i18n/sq.po Normal file

File diff suppressed because it is too large Load Diff

5422
i18n/sr.po Normal file

File diff suppressed because it is too large Load Diff

4764
i18n/sr@latin.po Normal file

File diff suppressed because it is too large Load Diff

5304
i18n/sv.po Normal file

File diff suppressed because it is too large Load Diff

5458
i18n/th.po Normal file

File diff suppressed because it is too large Load Diff

5459
i18n/tr.po Normal file

File diff suppressed because it is too large Load Diff

5482
i18n/uk.po Normal file

File diff suppressed because it is too large Load Diff

5470
i18n/vi.po Normal file

File diff suppressed because it is too large Load Diff

5327
i18n/zh_CN.po Normal file

File diff suppressed because it is too large Load Diff

5326
i18n/zh_TW.po Normal file

File diff suppressed because it is too large Load Diff

25
models/__init__.py Normal file
View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import ir_http
from . import ir_mail_server
from . import ir_model
from . import link_tracker
from . import mail_blacklist
from . import mailing_subscription # keep before due to decorated m2m
from . import mailing_contact
from . import mailing_list
from . import mailing_subscription_optout
from . import mailing_trace
from . import mailing
from . import mailing_filter
from . import mail_mail
from . import mail_render_mixin
from . import mail_thread
from . import res_company
from . import res_config_settings
from . import res_partner
from . import res_users
from . import utm_campaign
from . import utm_medium
from . import utm_source

12
models/ir_http.py Normal file
View File

@ -0,0 +1,12 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
class IrHttp(models.AbstractModel):
_inherit = "ir.http"
@classmethod
def _get_translation_frontend_modules_name(cls):
mods = super()._get_translation_frontend_modules_name()
return mods + ["mass_mailing"]

36
models/ir_mail_server.py Normal file
View File

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, fields, models
from odoo.tools.misc import format_date
class IrMailServer(models.Model):
_name = 'ir.mail_server'
_inherit = ['ir.mail_server']
active_mailing_ids = fields.One2many(
comodel_name='mailing.mailing',
inverse_name='mail_server_id',
string='Active mailing using this mail server',
readonly=True,
domain=[('state', '!=', 'done'), ('active', '=', True)])
def _active_usages_compute(self):
def format_usage(mailing_id):
base = _('Mass Mailing "%s"', mailing_id.display_name)
if not mailing_id.schedule_date:
return base
details = _('(scheduled for %s)', format_date(self.env, mailing_id.schedule_date))
return f'{base} {details}'
usages_super = super(IrMailServer, self)._active_usages_compute()
default_mail_server_id = self.env['mailing.mailing']._get_default_mail_server_id()
for record in self:
usages = []
if default_mail_server_id == record.id:
usages.append(_('Email Marketing uses it as its default mail server to send mass mailings'))
usages.extend(map(format_usage, record.active_mailing_ids))
if usages:
usages_super.setdefault(record.id, []).extend(usages)
return usages_super

34
models/ir_model.py Normal file
View File

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, _
class IrModel(models.Model):
_inherit = 'ir.model'
is_mailing_enabled = fields.Boolean(
string="Mailing Enabled",
compute='_compute_is_mailing_enabled', search='_search_is_mailing_enabled',
help="Whether this model supports marketing mailing capabilities (notably email and SMS).",
)
def _compute_is_mailing_enabled(self):
for model in self:
model.is_mailing_enabled = getattr(self.env[model.model], '_mailing_enabled', False)
def _search_is_mailing_enabled(self, operator, value):
if operator not in ('=', '!='):
raise ValueError(_("Searching Mailing Enabled models supports only direct search using '='' or '!='."))
valid_models = self.env['ir.model']
for model in self.search([]):
if model.model not in self.env or model.is_transient():
continue
if getattr(self.env[model.model], '_mailing_enabled', False):
valid_models |= model
search_is_mailing_enabled = (operator == '=' and value) or (operator == '!=' and not value)
if search_is_mailing_enabled:
return [('id', 'in', valid_models.ids)]
return [('id', 'not in', valid_models.ids)]

42
models/link_tracker.py Normal file
View File

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class LinkTracker(models.Model):
_inherit = "link.tracker"
mass_mailing_id = fields.Many2one('mailing.mailing', string='Mass Mailing')
class LinkTrackerClick(models.Model):
_inherit = "link.tracker.click"
mailing_trace_id = fields.Many2one('mailing.trace', string='Mail Statistics')
mass_mailing_id = fields.Many2one('mailing.mailing', string='Mass Mailing')
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('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('campaign_id'):
click_values['campaign_id'] = trace_sudo.campaign_id.id
if not click_values.get('mass_mailing_id'):
click_values['mass_mailing_id'] = trace_sudo.mass_mailing_id.id
return click_values
@api.model
def add_click(self, code, **route_values):
click = super(LinkTrackerClick, self).add_click(code, **route_values)
if click and click.mailing_trace_id:
click.mailing_trace_id.set_opened()
click.mailing_trace_id.set_clicked()
return click

20
models/mail_blacklist.py Normal file
View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class MailBlackList(models.Model):
""" Model of blacklisted email addresses to stop sending emails."""
_inherit = ['mail.blacklist']
opt_out_reason_id = fields.Many2one(
'mailing.subscription.optout', string='Reason',
ondelete='restrict',
tracking=10)
def _track_subtype(self, init_values):
self.ensure_one()
if 'opt_out_reason_id' in init_values and self.opt_out_reason_id:
return self.env.ref('mail.mt_comment')
return super()._track_subtype(init_values)

Some files were not shown because too many files have changed in this diff Show More