Merge PR #841 into 15.0

Signed-off-by dreispt
This commit is contained in:
OCA-git-bot 2022-03-03 15:28:24 +00:00
commit 3b491aa4e7
19 changed files with 1391 additions and 0 deletions

View File

@ -0,0 +1,110 @@
====================
Mail Outbound Static
====================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github
:target: https://github.com/OCA/social/tree/15.0/mail_outbound_static
:alt: OCA/social
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/social-15-0/social-15-0-mail_outbound_static
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/205/15.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
This module brings Odoo outbound emails in to strict compliance with RFC-2822
by allowing for a dynamically configured From header, with the sender's e-mail
being appended into the proper Sender header instead. To accomplish this we:
* Add a domain whitelist field in the mail server model. This one represent an
allowed Domains list separated by commas. If there is not given SMTP server
it will let us to search the proper mail server to be used to send the messages
where the message 'From' email domain match with the domain whitelist. If
there is not mail server that matches then will use the default mail server to
send the message.
* Add a Email From field that will let us to email from a specific address taking
into account this conditions:
1) If the sender domain match with the domain whitelist then the original
message's 'From' will remain as it is and will not be changed because the
mail server is able to send in the name of the sender domain.
2) If the original message's 'From' does not match with the domain whitelist
then the email From is replaced with the Email From field value.
* Add compatibility to define the smtp information in Odoo config file. Both
smtp_from and smtp_whitelist_domain values will be used if there is not mail
server configured in the system.
**Table of contents**
.. contents::
:local:
Usage
=====
* Navigate to an Outbound Email Server
* Set the `Email From` option to an email address
* Set the `Domain Whitelist` option with the domain whitelist
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/social/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/social/issues/new?body=module:%20mail_outbound_static%0Aversion:%2015.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* brain-tec AG
* LasLabs
* Adhoc SA
Contributors
~~~~~~~~~~~~
* Frédéric Garbely <frederic.garbely@braintec-group.com>
* Dave Lasley <dave@laslabs.com>
* Lorenzo Battistini <https://github.com/eLBati>
* Pierre Pizzetta <pierre@devreaction.com>
* Katherine Zaoral <kz@adhoc.com.ar>
* Juan José Scarafía <jjs@adhoc.com.ar>
Maintainers
~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the `OCA/social <https://github.com/OCA/social/tree/15.0/mail_outbound_static>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@ -0,0 +1,4 @@
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from . import models

View File

@ -0,0 +1,16 @@
# Copyright 2016-2022 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
{
"name": "Mail Outbound Static",
"summary": "Allows you to configure the from header for a mail server.",
"version": "15.0.1.0.0",
"category": "Discuss",
"website": "https://github.com/OCA/social",
"author": "brain-tec AG, LasLabs, Adhoc SA, Odoo Community Association (OCA)",
"license": "LGPL-3",
"application": False,
"installable": True,
"depends": ["base"],
"data": ["views/ir_mail_server_view.xml"],
}

View File

@ -0,0 +1,73 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * mail_outbound_static
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 13.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-03 12:53+0000\n"
"PO-Revision-Date: 2020-09-03 12:53+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: mail_outbound_static
#: code:addons/mail_outbound_static/models/ir_mail_server.py:0
#, python-format
msgid ""
"%s is not a valid domain. Please define a list of valid domains separated by"
" comma"
msgstr ""
"%s no es un dominio válido. Por favor defina una lista de dominios validos separados por"
" comas"
#. module: mail_outbound_static
#: model:ir.model.fields,help:mail_outbound_static.field_ir_mail_server__domain_whitelist
msgid ""
"Allowed Domains list separated by commas. If there is not given SMTP server "
"it will let us to search the proper mail server to be used to sent the "
"messages where the message 'From' email domain match with the domain "
"whitelist."
msgstr ""
"Lista de dominios permitidos separados por comas. Si no se ha seleccionado "
"un servidor SMTP nos permitirá seleccionar el servidor de mail apropiado "
"para enviar los mensajes donde el dominio del email del 'De' coincida con la"
" lista blanca de dominios."
#. module: mail_outbound_static
#: model:ir.model.fields,field_description:mail_outbound_static.field_ir_mail_server__domain_whitelist
msgid "Domain Whitelist"
msgstr "Lista blanca de dominios"
#. module: mail_outbound_static
#: model:ir.model.fields,field_description:mail_outbound_static.field_ir_mail_server__smtp_from
msgid "Email From"
msgstr "Email De"
#. module: mail_outbound_static
#: model:ir.model,name:mail_outbound_static.model_ir_mail_server
msgid "Mail Server"
msgstr "Servidor de correo"
#. module: mail_outbound_static
#: code:addons/mail_outbound_static/models/ir_mail_server.py:0
#, python-format
msgid "Not a valid Email From"
msgstr "No es un Email De válido"
#. module: mail_outbound_static
#: model:ir.model.fields,help:mail_outbound_static.field_ir_mail_server__smtp_from
msgid ""
"Set this in order to email from a specific address. If the original "
"message's 'From' does not match with the domain whitelist then it is "
"replaced with this value. If does match with the domain whitelist then the "
"original message's 'From' will not change"
msgstr ""
"Definalo para usar un dirección de correo 'De' especifica. Si el 'De' del "
"mensaje original no coincide con la lista blanca de dominios entonces este "
"será remplazado con este valor. Si coincide con la lista blanca de dominios "
"entonces el 'De' del mensajee original no cambiará"

View File

@ -0,0 +1,78 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * mail_outbound_static
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-03 12:53+0000\n"
"PO-Revision-Date: 2020-09-03 12:53+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: mail_outbound_static
#: model:ir.model.fields,field_description:mail_outbound_static.field_ir_mail_server__display_name
msgid "Display Name"
msgstr ""
#. module: mail_outbound_static
#: code:addons/mail_outbound_static/models/ir_mail_server.py:0
#, python-format
msgid ""
"%s is not a valid domain. Please define a list of valid domains separated by"
" comma"
msgstr ""
#. module: mail_outbound_static
#: model:ir.model.fields,help:mail_outbound_static.field_ir_mail_server__domain_whitelist
msgid ""
"Allowed Domains list separated by commas. If there is not given SMTP server "
"it will let us to search the proper mail server to be used to sent the "
"messages where the message 'From' email domain match with the domain "
"whitelist."
msgstr ""
#. module: mail_outbound_static
#: model:ir.model.fields,field_description:mail_outbound_static.field_ir_mail_server__domain_whitelist
msgid "Domain Whitelist"
msgstr ""
#. module: mail_outbound_static
#: model:ir.model.fields,field_description:mail_outbound_static.field_ir_mail_server__smtp_from
msgid "Email From"
msgstr ""
#. module: mail_outbound_static
#: model:ir.model.fields,field_description:mail_outbound_static.field_ir_mail_server__id
msgid "ID"
msgstr ""
#. module: mail_outbound_static
#: model:ir.model.fields,field_description:mail_outbound_static.field_ir_mail_server____last_update
msgid "Last Modified on"
msgstr ""
#. module: mail_outbound_static
#: model:ir.model,name:mail_outbound_static.model_ir_mail_server
msgid "Mail Server"
msgstr ""
#. module: mail_outbound_static
#: code:addons/mail_outbound_static/models/ir_mail_server.py:0
#, python-format
msgid "Not a valid Email From"
msgstr ""
#. module: mail_outbound_static
#: model:ir.model.fields,help:mail_outbound_static.field_ir_mail_server__smtp_from
msgid ""
"Set this in order to email from a specific address. If the original "
"message's 'From' does not match with the domain whitelist then it is "
"replaced with this value. If does match with the domain whitelist then the "
"original message's 'From' will not change"
msgstr ""

View File

@ -0,0 +1,4 @@
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from . import ir_mail_server

View File

@ -0,0 +1,146 @@
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import re
from email.utils import formataddr, parseaddr
from odoo import _, api, fields, models, tools
from odoo.exceptions import ValidationError
class IrMailServer(models.Model):
_inherit = "ir.mail_server"
smtp_from = fields.Char(
string="Email From",
help="Set this in order to email from a specific address."
" If the original message's 'From' does not match with the domain"
" whitelist then it is replaced with this value. If does match with the"
" domain whitelist then the original message's 'From' will not change",
)
domain_whitelist = fields.Char(
help="Allowed Domains list separated by commas. If there is not given"
" SMTP server it will let us to search the proper mail server to be"
" used to sent the messages where the message 'From' email domain"
" match with the domain whitelist."
)
@api.constrains("domain_whitelist")
def check_valid_domain_whitelist(self):
if self.domain_whitelist:
domains = list(self.domain_whitelist.split(","))
for domain in domains:
if not self._is_valid_domain(domain):
raise ValidationError(
_(
"%s is not a valid domain. Please define a list of"
" valid domains separated by comma"
)
% (domain)
)
@api.constrains("smtp_from")
def check_valid_smtp_from(self):
if self.smtp_from:
match = re.match(
r"^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\."
r"[a-z]{2,4})$",
self.smtp_from,
)
if match is None:
raise ValidationError(_("Not a valid Email From"))
def _is_valid_domain(self, domain_name):
domain_regex = (
r"(([\da-zA-Z])([_\w-]{,62})\.){,127}(([\da-zA-Z])"
r"[_\w-]{,61})?([\da-zA-Z]\.((xn\-\-[a-zA-Z\d]+)|([a-zA-Z\d]{2,})))"
)
domain_regex = "{}$".format(domain_regex)
valid_domain_name_regex = re.compile(domain_regex, re.IGNORECASE)
domain_name = domain_name.lower().strip()
return True if re.match(valid_domain_name_regex, domain_name) else False
@api.model
def _get_domain_whitelist(self, domain_whitelist_string):
res = domain_whitelist_string.split(",") if domain_whitelist_string else []
res = [item.strip() for item in res]
return res
@api.model
def send_email(
self, message, mail_server_id=None, smtp_server=None, *args, **kwargs
):
# Get email_from and name_from
if message["From"].count("<") > 1:
split_from = message["From"].rsplit(" <", 1)
name_from = split_from[0]
email_from = split_from[-1].replace(">", "")
else:
name_from, email_from = parseaddr(message["From"])
email_domain = email_from.split("@")[1]
# Replicate logic from core to get mail server
# Get proper mail server to use
if not smtp_server and not mail_server_id:
mail_server_id = self._get_mail_sever(email_domain)
# If not mail sever defined use smtp_from defined in odoo config
if mail_server_id:
mail_server = self.sudo().browse(mail_server_id)
domain_whitelist = mail_server.domain_whitelist
smtp_from = mail_server.smtp_from
else:
domain_whitelist = tools.config.get("smtp_domain_whitelist")
smtp_from = tools.config.get("smtp_from")
domain_whitelist = self._get_domain_whitelist(domain_whitelist)
# Replace the From only if needed
if smtp_from and (not domain_whitelist or email_domain not in domain_whitelist):
email_from = formataddr((name_from, smtp_from))
message.replace_header("From", email_from)
bounce_alias = (
self.env["ir.config_parameter"].sudo().get_param("mail.bounce.alias")
)
if not bounce_alias:
# then, bounce handling is disabled and we want
# Return-Path = From
if "Return-Path" in message:
message.replace_header("Return-Path", email_from)
else:
message.add_header("Return-Path", email_from)
return super(IrMailServer, self).send_email(
message, mail_server_id, smtp_server, *args, **kwargs
)
@tools.ormcache("email_domain")
def _get_mail_sever(self, email_domain):
"""return the mail server id that match with the domain_whitelist
If not match then return the default mail server id available one"""
mail_server_id = None
for item in self.sudo().search(
[("domain_whitelist", "!=", False)], order="sequence"
):
domain_whitelist = self._get_domain_whitelist(item.domain_whitelist)
if email_domain in domain_whitelist:
mail_server_id = item.id
break
if not mail_server_id:
mail_server_id = self.sudo().search([], order="sequence", limit=1).id
return mail_server_id
@api.model
def create(self, values):
self.clear_caches()
return super().create(values)
def write(self, values):
self.clear_caches()
return super().write(values)
def unlink(self):
self.clear_caches()
return super().unlink()

View File

@ -0,0 +1,6 @@
* Frédéric Garbely <frederic.garbely@braintec-group.com>
* Dave Lasley <dave@laslabs.com>
* Lorenzo Battistini <https://github.com/eLBati>
* Pierre Pizzetta <pierre@devreaction.com>
* Katherine Zaoral <kz@adhoc.com.ar>
* Juan José Scarafía <jjs@adhoc.com.ar>

View File

@ -0,0 +1,24 @@
This module brings Odoo outbound emails in to strict compliance with RFC-2822
by allowing for a dynamically configured From header, with the sender's e-mail
being appended into the proper Sender header instead. To accomplish this we:
* Add a domain whitelist field in the mail server model. This one represent an
allowed Domains list separated by commas. If there is not given SMTP server
it will let us to search the proper mail server to be used to send the messages
where the message 'From' email domain match with the domain whitelist. If
there is not mail server that matches then will use the default mail server to
send the message.
* Add a Email From field that will let us to email from a specific address taking
into account this conditions:
1) If the sender domain match with the domain whitelist then the original
message's 'From' will remain as it is and will not be changed because the
mail server is able to send in the name of the sender domain.
2) If the original message's 'From' does not match with the domain whitelist
then the email From is replaced with the Email From field value.
* Add compatibility to define the smtp information in Odoo config file. Both
smtp_from and smtp_whitelist_domain values will be used if there is not mail
server configured in the system.

View File

View File

@ -0,0 +1,3 @@
* Navigate to an Outbound Email Server
* Set the `Email From` option to an email address
* Set the `Domain Whitelist` option with the domain whitelist

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -0,0 +1,457 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.15.1: http://docutils.sourceforge.net/" />
<title>Mail Outbound Static</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="mail-outbound-static">
<h1 class="title">Mail Outbound Static</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/social/tree/15.0/mail_outbound_static"><img alt="OCA/social" src="https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/social-15-0/social-15-0-mail_outbound_static"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/205/15.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>This module brings Odoo outbound emails in to strict compliance with RFC-2822
by allowing for a dynamically configured From header, with the senders e-mail
being appended into the proper Sender header instead. To accomplish this we:</p>
<ul class="simple">
<li>Add a domain whitelist field in the mail server model. This one represent an
allowed Domains list separated by commas. If there is not given SMTP server
it will let us to search the proper mail server to be used to send the messages
where the message From email domain match with the domain whitelist. If
there is not mail server that matches then will use the default mail server to
send the message.</li>
<li>Add a Email From field that will let us to email from a specific address taking
into account this conditions:<ol class="arabic">
<li>If the sender domain match with the domain whitelist then the original
messages From will remain as it is and will not be changed because the
mail server is able to send in the name of the sender domain.</li>
<li>If the original messages From does not match with the domain whitelist
then the email From is replaced with the Email From field value.</li>
</ol>
</li>
<li>Add compatibility to define the smtp information in Odoo config file. Both
smtp_from and smtp_whitelist_domain values will be used if there is not mail
server configured in the system.</li>
</ul>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="id1">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id2">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id3">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id4">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id5">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id6">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id1">Usage</a></h1>
<ul class="simple">
<li>Navigate to an Outbound Email Server</li>
<li>Set the <cite>Email From</cite> option to an email address</li>
<li>Set the <cite>Domain Whitelist</cite> option with the domain whitelist</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id2">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/social/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/social/issues/new?body=module:%20mail_outbound_static%0Aversion:%2015.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id3">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id4">Authors</a></h2>
<ul class="simple">
<li>brain-tec AG</li>
<li>LasLabs</li>
<li>Adhoc SA</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id5">Contributors</a></h2>
<ul class="simple">
<li>Frédéric Garbely &lt;<a class="reference external" href="mailto:frederic.garbely&#64;braintec-group.com">frederic.garbely&#64;braintec-group.com</a>&gt;</li>
<li>Dave Lasley &lt;<a class="reference external" href="mailto:dave&#64;laslabs.com">dave&#64;laslabs.com</a>&gt;</li>
<li>Lorenzo Battistini &lt;<a class="reference external" href="https://github.com/eLBati">https://github.com/eLBati</a>&gt;</li>
<li>Pierre Pizzetta &lt;<a class="reference external" href="mailto:pierre&#64;devreaction.com">pierre&#64;devreaction.com</a>&gt;</li>
<li>Katherine Zaoral &lt;<a class="reference external" href="mailto:kz&#64;adhoc.com.ar">kz&#64;adhoc.com.ar</a>&gt;</li>
<li>Juan José Scarafía &lt;<a class="reference external" href="mailto:jjs&#64;adhoc.com.ar">jjs&#64;adhoc.com.ar</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id6">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/social/tree/15.0/mail_outbound_static">OCA/social</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,4 @@
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from . import test_ir_mail_server

View File

@ -0,0 +1,70 @@
Delivered-To: test@gmail.com
Received: by 10.74.138.167 with SMTP id m36csp7226976ooj;
Tue, 12 Sep 2017 10:37:56 -0700 (PDT)
X-Google-Smtp-Source: AOwi7QDKSb3BE6lIhVXub9wcPA/HxFKKpnNPconNr9f1L35SVw+EIm8itVQkbOdAW6TohImypmrF
X-Received: by 10.28.158.208 with SMTP id h199mr250060wme.47.1505237876258;
Tue, 12 Sep 2017 10:37:56 -0700 (PDT)
ARC-Seal: i=1; a=rsa-sha256; t=1505237876; cv=none;
d=google.com; s=arc-20160816;
b=E2B6KUxHOJQk1YrT12BpitEMCgkxyqEXcFlwPWKjA/i/Xyvlh+09spNOF4VPmD/ZJm
5lkY6hYyxvIH2RpRPeZVPkRIYhaEASkMIygdJu9Gd4weBdO2rd8iP/zSGHYyAmO/hLN2
64hXtKexrWnO/YNWlpfhAo1kiwgSRVnZx55EopbWP49cy7BzKfwr1kHN0T9A5Lw1w+BW
ZrXdCX6LRxHS2USKHb76PAVt0bhwsM/ZznBauR2zNKYcPxAWzdpN/vK3BDmWUdqbbSaB
BOKINjuI9EmWynogDZE7Riu+sbc5QafE3owla1/2d0Bogp9FLtJe0YyQeW2qLvZKcmlI
ftSQ==
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816;
h=to:list-archive:list-unsubscribe:list-subscribe:precedence
:list-post:list-id:date:reply-to:from:subject:references:message-id
:mime-version:arc-authentication-results;
bh=DwvSiw5K7ryb4S8O/8HcIaGhJqbOxcXKsnPAr63iQZ4=;
b=U0Ac9Rqvv+tfqO9fCx+F79oZknn3rOv9N9ekViEuL5DtjpJxKDDkO1xw//sV3eRILT
nqGuxd2yQXwC4U+WAwraBwoLC3ScHb/9gWtzlrLCgv6WbNE7HZi5g6L8c0LWRN24cIe9
AOdc/8fOdGoaL8yajrGEHgMz9B2KMltA9tZyxFOeKsyODxJ6iWjXcG1BSQTxERwosV3h
ch8AznQr7xLLvc/u9VTEqC5ome3RqsxKRxOGenEqIbCOr11sxwpZQdQcNR6faNRom3+2
6gz++4tVIV9cqYX1j9eEU/ufoUzBJ6Uzm0jMGZZQOHAF+YX3tZUEsPmc75PsvRCAIWby
urMg==
ARC-Authentication-Results: i=1; mx.google.com;
spf=pass (google.com: domain of postmaster-odoo@odoo-community.org designates 2a01:4f8:a0:430d::2 as permitted sender) smtp.mailfrom=postmaster-odoo@odoo-community.org;
dmarc=fail (p=QUARANTINE sp=QUARANTINE dis=QUARANTINE) header.from=laslabs.com
Return-Path: <postmaster-odoo@odoo-community.org>
Received: from odoo-community.org (odoo-community.org. [2a01:4f8:a0:430d::2])
by mx.google.com with ESMTP id j72si1795626wmg.60.2017.09.12.10.37.55
for <test@gmail.com>;
Tue, 12 Sep 2017 10:37:56 -0700 (PDT)
Received-SPF: pass (google.com: domain of postmaster-odoo@odoo-community.org designates 2a01:4f8:a0:430d::2 as permitted sender) client-ip=2a01:4f8:a0:430d::2;
Authentication-Results: mx.google.com;
spf=pass (google.com: domain of postmaster-odoo@odoo-community.org designates 2a01:4f8:a0:430d::2 as permitted sender) smtp.mailfrom=postmaster-odoo@odoo-community.org;
dmarc=fail (p=QUARANTINE sp=QUARANTINE dis=QUARANTINE) header.from=laslabs.com
Received: from odoo.odoo-community.org (localhost.localdomain [127.0.0.1])
by odoo-community.org (Postfix) with ESMTP id DB5DC2EC2277;
Tue, 12 Sep 2017 19:37:53 +0200 (CEST)
Content-Type: multipart/mixed; boundary="===============7439524030966430607=="
MIME-Version: 1.0
Message-Id: <A1AAECD6-2A36-45E1-A0F8-4266F178D52C@laslabs.com>
references: <0db43737-b846-4890-6801-44ff9617e3b3@camptocamp.com>
Subject: Re: OCA Code sprint: sprint topics
From: Dave Lasley <test@laslabs.com>
Reply-To: "Odoo Community Association \(OCA\) Contributors"
<contributors@odoo-community.org>
Date: Tue, 12 Sep 2017 17:37:53 -0000
List-Id: contributors.odoo-community.org
List-Post: <mailto:contributors@odoo-community.org>
Precedence: list
X-Auto-Response-Suppress: OOF
List-Subscribe: <https://odoo-community.org/groups>
List-Unsubscribe: <https://odoo-community.org/groups?unsubscribe>
List-Archive: <https://odoo-community.org/groups/contributors-15>
To: "Contributors" <contributors@odoo-community.org>
--===============7439524030966430607==
Content-Type: multipart/alternative;
boundary="===============8317593469411551167=="
MIME-Version: 1.0
--===============8317593469411551167==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
VGhpcyBpcyBhIGZha2UsIHRlc3QgbWVzc2FnZQ==
--===============7439524030966430607==--

View File

@ -0,0 +1,369 @@
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import logging
import os
from email import message_from_string
import odoo.tools as tools
from odoo.exceptions import ValidationError
from odoo.tests.common import TransactionCase
from odoo.addons.base.tests.common import MockSmtplibCase
_logger = logging.getLogger(__name__)
class TestIrMailServer(TransactionCase, MockSmtplibCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.email_from = "derp@example.com"
cls.email_from_another = "another@example.com"
cls.IrMailServer = cls.env["ir.mail_server"]
cls.parameter_model = cls.env["ir.config_parameter"]
cls._delete_mail_servers()
cls.IrMailServer.create(
{
"name": "localhost",
"smtp_host": "localhost",
"smtp_from": cls.email_from,
}
)
message_file = os.path.join(
os.path.dirname(os.path.realpath(__file__)), "test.msg"
)
with open(message_file, "r") as fh:
cls.message = message_from_string(fh.read())
@classmethod
def _delete_mail_servers(cls):
"""Delete all available mail servers"""
all_mail_servers = cls.IrMailServer.search([])
if all_mail_servers:
all_mail_servers.unlink()
def _init_mail_server_domain_whilelist_based(self):
self._delete_mail_servers()
self.assertFalse(self.IrMailServer.search([]))
self.mail_server_domainone = self.IrMailServer.create(
{
"name": "sandbox domainone",
"smtp_host": "localhost",
"smtp_from": "notifications@domainone.com",
"domain_whitelist": "domainone.com",
}
)
self.mail_server_domaintwo = self.IrMailServer.create(
{
"name": "sandbox domaintwo",
"smtp_host": "localhost",
"smtp_from": "hola@domaintwo.com",
"domain_whitelist": "domaintwo.com",
}
)
self.mail_server_domainthree = self.IrMailServer.create(
{
"name": "sandbox domainthree",
"smtp_host": "localhost",
"smtp_from": "notifications@domainthree.com",
"domain_whitelist": "domainthree.com,domainmulti.com",
}
)
def _skip_test(self, reason):
_logger.warn(reason)
self.skipTest(reason)
def _send_mail(
self,
message,
mail_server_id=None,
smtp_server=None,
smtp_port=None,
smtp_user=None,
smtp_password=None,
smtp_encryption=None,
smtp_ssl_certificate=None,
smtp_ssl_private_key=None,
smtp_debug=False,
smtp_session=None,
):
smtp = smtp_session
if not smtp:
smtp = self.IrMailServer.connect(
smtp_server,
smtp_port,
smtp_user,
smtp_password,
smtp_encryption,
smtp_from=message["From"],
ssl_certificate=smtp_ssl_certificate,
ssl_private_key=smtp_ssl_private_key,
smtp_debug=smtp_debug,
mail_server_id=mail_server_id,
)
send_from, send_to, message_string = self.IrMailServer._prepare_email_message(
message, smtp
)
self.IrMailServer.send_email(message)
return message_string
def test_send_email_injects_from_no_canonical(self):
"""It should inject the FROM header correctly when no canonical name."""
self.message.replace_header("From", "test@example.com")
# A mail server is configured for the email
with self.mock_smtplib_connection():
message = self._send_mail(self.message)
self.assertEqual(message["From"], self.email_from)
def test_send_email_injects_from_with_canonical(self):
"""It should inject the FROM header correctly with a canonical name.
Note that there is an extra `<` in the canonical name to test for
proper handling in the split.
"""
user = "Test < User"
self.message.replace_header("From", "%s <test@example.com>" % user)
bounce_parameter = self.parameter_model.search(
[("key", "=", "mail.bounce.alias")]
)
if bounce_parameter:
# Remove mail.bounce.alias to test Return-Path
bounce_parameter.unlink()
# Also check passing mail_server_id
mail_server_id = (
self.IrMailServer.sudo().search([], order="sequence", limit=1)[0].id
)
# A mail server is configured for the email
with self.mock_smtplib_connection():
message = self._send_mail(self.message, mail_server_id=mail_server_id)
self.assertEqual(message["From"], '"{}" <{}>'.format(user, self.email_from))
self.assertEqual(
message["Return-Path"], '"{}" <{}>'.format(user, self.email_from)
)
def test_01_from_outgoing_server_domainone(self):
self._init_mail_server_domain_whilelist_based()
domain = "domainone.com"
email_from = "Mitchell Admin <admin@%s>" % domain
expected_mail_server = self.mail_server_domainone
self.message.replace_header("From", email_from)
# A mail server is configured for the email
with self.mock_smtplib_connection():
message = self._send_mail(self.message)
self.assertEqual(message["From"], email_from)
used_mail_server = self.IrMailServer._get_mail_sever(domain)
used_mail_server = self.IrMailServer.browse(used_mail_server)
self.assertEqual(
used_mail_server,
expected_mail_server,
"It using %s but we expect to use %s"
% (used_mail_server.name, expected_mail_server.name),
)
def test_02_from_outgoing_server_domaintwo(self):
self._init_mail_server_domain_whilelist_based()
domain = "domaintwo.com"
email_from = "Mitchell Admin <admin@%s>" % domain
expected_mail_server = self.mail_server_domaintwo
self.message.replace_header("From", email_from)
# A mail server is configured for the email
with self.mock_smtplib_connection():
message = self._send_mail(self.message)
self.assertEqual(message["From"], email_from)
used_mail_server = self.IrMailServer._get_mail_sever(domain)
used_mail_server = self.IrMailServer.browse(used_mail_server)
self.assertEqual(
used_mail_server,
expected_mail_server,
"It using %s but we expect to use %s"
% (used_mail_server.name, expected_mail_server.name),
)
def test_03_from_outgoing_server_another(self):
self._init_mail_server_domain_whilelist_based()
domain = "example.com"
email_from = "Mitchell Admin <admin@%s>" % domain
expected_mail_server = self.mail_server_domainone
self.message.replace_header("From", email_from)
# A mail server is configured for the email
with self.mock_smtplib_connection():
message = self._send_mail(self.message)
self.assertEqual(
message["From"], "Mitchell Admin <%s>" % expected_mail_server.smtp_from
)
used_mail_server = self.IrMailServer._get_mail_sever(domain)
used_mail_server = self.IrMailServer.browse(used_mail_server)
self.assertEqual(
used_mail_server,
expected_mail_server,
"It using %s but we expect to use %s"
% (used_mail_server.name, expected_mail_server.name),
)
def test_04_from_outgoing_server_none_use_config(self):
self._init_mail_server_domain_whilelist_based()
domain = "example.com"
email_from = "Mitchell Admin <admin@%s>" % domain
self._delete_mail_servers()
self.assertFalse(self.IrMailServer.search([]))
# Find config values
config_smtp_from = tools.config.get("smtp_from")
config_smtp_domain_whitelist = tools.config.get("smtp_domain_whitelist")
if not config_smtp_from or not config_smtp_domain_whitelist:
self._skip_test(
"Cannot test transactions because there is not either smtp_from"
" or smtp_domain_whitelist."
)
self.message.replace_header("From", email_from)
# A mail server is configured for the email
with self.mock_smtplib_connection():
message = self._send_mail(self.message)
self.assertEqual(message["From"], "Mitchell Admin <%s>" % config_smtp_from)
used_mail_server = self.IrMailServer._get_mail_sever("example.com")
used_mail_server = self.IrMailServer.browse(used_mail_server)
self.assertFalse(
used_mail_server, "using this mail server %s" % (used_mail_server.name)
)
def test_05_from_outgoing_server_none_same_domain(self):
self._init_mail_server_domain_whilelist_based()
# Find config values
config_smtp_from = tools.config.get("smtp_from")
config_smtp_domain_whitelist = domain = tools.config.get(
"smtp_domain_whitelist"
)
if not config_smtp_from or not config_smtp_domain_whitelist:
self._skip_test(
"Cannot test transactions because there is not either smtp_from"
" or smtp_domain_whitelist."
)
email_from = "Mitchell Admin <admin@%s>" % domain
self._delete_mail_servers()
self.assertFalse(self.IrMailServer.search([]))
self.message.replace_header("From", email_from)
# A mail server is configured for the email
with self.mock_smtplib_connection():
message = self._send_mail(self.message)
self.assertEqual(message["From"], email_from)
used_mail_server = self.IrMailServer._get_mail_sever(domain)
used_mail_server = self.IrMailServer.browse(used_mail_server)
self.assertFalse(used_mail_server)
def test_06_from_outgoing_server_no_name_from(self):
self._init_mail_server_domain_whilelist_based()
domain = "example.com"
email_from = "test@%s" % domain
expected_mail_server = self.mail_server_domainone
self.message.replace_header("From", email_from)
# A mail server is configured for the email
with self.mock_smtplib_connection():
message = self._send_mail(self.message)
self.assertEqual(message["From"], expected_mail_server.smtp_from)
used_mail_server = self.IrMailServer._get_mail_sever(domain)
used_mail_server = self.IrMailServer.browse(used_mail_server)
self.assertEqual(
used_mail_server,
expected_mail_server,
"It using %s but we expect to use %s"
% (used_mail_server.name, expected_mail_server.name),
)
def test_07_from_outgoing_server_multidomain_1(self):
self._init_mail_server_domain_whilelist_based()
domain = "domainthree.com"
email_from = "Mitchell Admin <admin@%s>" % domain
expected_mail_server = self.mail_server_domainthree
self.message.replace_header("From", email_from)
# A mail server is configured for the email
with self.mock_smtplib_connection():
message = self._send_mail(self.message)
self.assertEqual(message["From"], email_from)
used_mail_server = self.IrMailServer._get_mail_sever(domain)
used_mail_server = self.IrMailServer.browse(used_mail_server)
self.assertEqual(
used_mail_server,
expected_mail_server,
"It using %s but we expect to use %s"
% (used_mail_server.name, expected_mail_server.name),
)
def test_08_from_outgoing_server_multidomain_3(self):
self._init_mail_server_domain_whilelist_based()
domain = "domainmulti.com"
email_from = "test@%s" % domain
expected_mail_server = self.mail_server_domainthree
self.message.replace_header("From", email_from)
# A mail server is configured for the email
with self.mock_smtplib_connection():
message = self._send_mail(self.message)
self.assertEqual(message["From"], email_from)
used_mail_server = self.IrMailServer._get_mail_sever(domain)
used_mail_server = self.IrMailServer.browse(used_mail_server)
self.assertEqual(
used_mail_server,
expected_mail_server,
"It using %s but we expect to use %s"
% (used_mail_server.name, expected_mail_server.name),
)
def test_09_not_valid_domain_whitelist(self):
self._init_mail_server_domain_whilelist_based()
mail_server = self.mail_server_domainone
mail_server.domain_whitelist = "example.com"
error_msg = (
"%s is not a valid domain. Please define a list of valid"
" domains separated by comma"
)
with self.assertRaisesRegex(ValidationError, error_msg % "asdasd"):
mail_server.domain_whitelist = "asdasd"
with self.assertRaisesRegex(ValidationError, error_msg % "asdasd"):
mail_server.domain_whitelist = "example.com, asdasd"
with self.assertRaisesRegex(ValidationError, error_msg % "invalid"):
mail_server.domain_whitelist = "example.com; invalid"
with self.assertRaisesRegex(ValidationError, error_msg % ";"):
mail_server.domain_whitelist = ";"
with self.assertRaisesRegex(ValidationError, error_msg % "."):
mail_server.domain_whitelist = "hola.com,."
def test_10_not_valid_smtp_from(self):
self._init_mail_server_domain_whilelist_based()
mail_server = self.mail_server_domainone
error_msg = "Not a valid Email From"
with self.assertRaisesRegex(ValidationError, error_msg):
mail_server.smtp_from = "asdasd"
with self.assertRaisesRegex(ValidationError, error_msg):
mail_server.smtp_from = "example.com"
with self.assertRaisesRegex(ValidationError, error_msg):
mail_server.smtp_from = "."
mail_server.smtp_from = "notifications@test.com"

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2017 LasLabs Inc.
License LGPL-3 or later (http://www.gnu.org/licenses/lgpl.html).
-->
<odoo>
<record id="ir_mail_server_form" model="ir.ui.view">
<field name="name">IR Mail Server - From Address</field>
<field name="model">ir.mail_server</field>
<field name="inherit_id" ref="base.ir_mail_server_form" />
<field name="arch" type="xml">
<xpath expr="//field[@name='smtp_pass']" position="after">
<field name="domain_whitelist" />
<field name="smtp_from" widget="email" />
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1 @@
../../../../mail_outbound_static

View File

@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)