fix: add auto-prefix for message subject when sending email
This commit is contained in:
parent
5de9c15bbd
commit
8a0830b35d
2 changed files with 203 additions and 26 deletions
|
|
@ -124,6 +124,14 @@ class EmailSetting: # pylint: disable=too-few-public-methods
|
||||||
|
|
||||||
default_subject = None
|
default_subject = None
|
||||||
|
|
||||||
|
default_prefix = None
|
||||||
|
"""
|
||||||
|
Default subject prefix for emails of this type.
|
||||||
|
|
||||||
|
Calling code should not access this directly, but instead use
|
||||||
|
:meth:`get_default_prefix()` .
|
||||||
|
"""
|
||||||
|
|
||||||
fallback_key = None
|
fallback_key = None
|
||||||
"""
|
"""
|
||||||
Optional fallback key to use for config/template lookup, if
|
Optional fallback key to use for config/template lookup, if
|
||||||
|
|
@ -147,6 +155,20 @@ class EmailSetting: # pylint: disable=too-few-public-methods
|
||||||
"""
|
"""
|
||||||
return self.__class__.__doc__.strip()
|
return self.__class__.__doc__.strip()
|
||||||
|
|
||||||
|
def get_default_prefix(self):
|
||||||
|
"""
|
||||||
|
This returns the default subject prefix, for sending emails of
|
||||||
|
this type.
|
||||||
|
|
||||||
|
Default logic here returns :attr:`default_prefix` as-is.
|
||||||
|
|
||||||
|
This method will often return ``None`` in which case the
|
||||||
|
global default prefix is used.
|
||||||
|
|
||||||
|
:returns: Default subject prefix as string, or ``None``
|
||||||
|
"""
|
||||||
|
return self.default_prefix
|
||||||
|
|
||||||
def get_default_subject(self):
|
def get_default_subject(self):
|
||||||
"""
|
"""
|
||||||
This must return the default subject, for sending emails of
|
This must return the default subject, for sending emails of
|
||||||
|
|
@ -434,7 +456,14 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods
|
||||||
return Message(**kwargs)
|
return Message(**kwargs)
|
||||||
|
|
||||||
def make_auto_message(
|
def make_auto_message(
|
||||||
self, key, context=None, default_subject=None, fallback_key=None, **kwargs
|
self,
|
||||||
|
key,
|
||||||
|
context=None,
|
||||||
|
default_subject=None,
|
||||||
|
prefix_subject=True,
|
||||||
|
default_prefix=None,
|
||||||
|
fallback_key=None,
|
||||||
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Make a new email message using config to determine its
|
Make a new email message using config to determine its
|
||||||
|
|
@ -455,6 +484,12 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods
|
||||||
:param default_subject: Optional :attr:`~Message.subject`
|
:param default_subject: Optional :attr:`~Message.subject`
|
||||||
template/string to use, if config does not specify one.
|
template/string to use, if config does not specify one.
|
||||||
|
|
||||||
|
:param prefix_subject: Boolean indicating the message subject
|
||||||
|
should be auto-prefixed.
|
||||||
|
|
||||||
|
:param default_prefix: Default subject prefix to use if none
|
||||||
|
is configured.
|
||||||
|
|
||||||
:param fallback_key: Optional fallback :term:`email key` to
|
:param fallback_key: Optional fallback :term:`email key` to
|
||||||
use for config/template lookup, if nothing is found for
|
use for config/template lookup, if nothing is found for
|
||||||
``key``.
|
``key``.
|
||||||
|
|
@ -483,7 +518,12 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods
|
||||||
kwargs["sender"] = self.get_auto_sender(key)
|
kwargs["sender"] = self.get_auto_sender(key)
|
||||||
if "subject" not in kwargs:
|
if "subject" not in kwargs:
|
||||||
kwargs["subject"] = self.get_auto_subject(
|
kwargs["subject"] = self.get_auto_subject(
|
||||||
key, context, default=default_subject, fallback_key=fallback_key
|
key,
|
||||||
|
context,
|
||||||
|
default=default_subject,
|
||||||
|
prefix=prefix_subject,
|
||||||
|
default_prefix=default_prefix,
|
||||||
|
fallback_key=fallback_key,
|
||||||
)
|
)
|
||||||
if "to" not in kwargs:
|
if "to" not in kwargs:
|
||||||
kwargs["to"] = self.get_auto_to(key)
|
kwargs["to"] = self.get_auto_to(key)
|
||||||
|
|
@ -567,16 +607,19 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods
|
||||||
key,
|
key,
|
||||||
context=None,
|
context=None,
|
||||||
rendered=True,
|
rendered=True,
|
||||||
setting=None,
|
|
||||||
default=None,
|
default=None,
|
||||||
fallback_key=None,
|
fallback_key=None,
|
||||||
|
setting=None,
|
||||||
|
prefix=True,
|
||||||
|
default_prefix=None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Returns automatic :attr:`~wuttjamaican.email.Message.subject`
|
Returns automatic :attr:`~wuttjamaican.email.Message.subject`
|
||||||
line for a message, as determined by config.
|
line for a message, as determined by config.
|
||||||
|
|
||||||
This calls :meth:`get_auto_subject_template()` and then
|
This calls :meth:`get_auto_subject_template()` and then
|
||||||
(usually) renders the result using the given context.
|
(usually) renders the result using the given context, and adds
|
||||||
|
the :meth:`get_auto_subject_prefix()`.
|
||||||
|
|
||||||
:param key: Key for the :term:`email type`. See also the
|
:param key: Key for the :term:`email type`. See also the
|
||||||
``fallback_key`` param, below.
|
``fallback_key`` param, below.
|
||||||
|
|
@ -588,15 +631,22 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods
|
||||||
template will be returned, instead of the final/rendered
|
template will be returned, instead of the final/rendered
|
||||||
subject text.
|
subject text.
|
||||||
|
|
||||||
:param setting: Optional :class:`EmailSetting` class or
|
|
||||||
instance. This is passed along to
|
|
||||||
:meth:`get_auto_subject_template()`.
|
|
||||||
|
|
||||||
:param default: Default subject to use if none is configured.
|
:param default: Default subject to use if none is configured.
|
||||||
|
|
||||||
:param fallback_key: Optional fallback :term:`email key` to
|
:param fallback_key: Optional fallback :term:`email key` to
|
||||||
use for config lookup, if nothing is found for ``key``.
|
use for config lookup, if nothing is found for ``key``.
|
||||||
|
|
||||||
|
:param setting: Optional :class:`EmailSetting` class or
|
||||||
|
instance. This is passed along to
|
||||||
|
:meth:`get_auto_subject_template()`.
|
||||||
|
|
||||||
|
:param prefix: Boolean indicating the message subject should
|
||||||
|
be auto-prefixed. This is ignored when ``rendered`` param
|
||||||
|
is false.
|
||||||
|
|
||||||
|
:param default_prefix: Default subject prefix to use if none
|
||||||
|
is configured.
|
||||||
|
|
||||||
:returns: Final subject text, either "raw" or rendered.
|
:returns: Final subject text, either "raw" or rendered.
|
||||||
"""
|
"""
|
||||||
template = self.get_auto_subject_template(
|
template = self.get_auto_subject_template(
|
||||||
|
|
@ -606,10 +656,18 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods
|
||||||
return template
|
return template
|
||||||
|
|
||||||
context = self.get_email_context(key, context)
|
context = self.get_email_context(key, context)
|
||||||
return Template(template).render(**context)
|
subject = Template(template).render(**context)
|
||||||
|
|
||||||
|
if prefix:
|
||||||
|
if prefix := self.get_auto_subject_prefix(
|
||||||
|
key, default=default_prefix, setting=setting, fallback_key=fallback_key
|
||||||
|
):
|
||||||
|
subject = f"{prefix} {subject}"
|
||||||
|
|
||||||
|
return subject
|
||||||
|
|
||||||
def get_auto_subject_template(
|
def get_auto_subject_template(
|
||||||
self, key, setting=None, default=None, fallback_key=None
|
self, key, default=None, fallback_key=None, setting=None
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Returns the template string to use for automatic subject line
|
Returns the template string to use for automatic subject line
|
||||||
|
|
@ -623,17 +681,17 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods
|
||||||
|
|
||||||
:param key: Key for the :term:`email type`.
|
:param key: Key for the :term:`email type`.
|
||||||
|
|
||||||
|
:param default: Default subject to use if none is configured.
|
||||||
|
|
||||||
|
:param fallback_key: Optional fallback :term:`email key` to
|
||||||
|
use for config lookup, if nothing is found for ``key``.
|
||||||
|
|
||||||
:param setting: Optional :class:`EmailSetting` class or
|
:param setting: Optional :class:`EmailSetting` class or
|
||||||
instance. This may be used to determine the "default"
|
instance. This may be used to determine the "default"
|
||||||
subject if none is configured. You can specify this as an
|
subject if none is configured. You can specify this as an
|
||||||
optimization; otherwise it will be fetched if needed via
|
optimization; otherwise it will be fetched if needed via
|
||||||
:meth:`get_email_setting()`.
|
:meth:`get_email_setting()`.
|
||||||
|
|
||||||
:param default: Default subject to use if none is configured.
|
|
||||||
|
|
||||||
:param fallback_key: Optional fallback :term:`email key` to
|
|
||||||
use for config lookup, if nothing is found for ``key``.
|
|
||||||
|
|
||||||
:returns: Final subject template, as raw text.
|
:returns: Final subject template, as raw text.
|
||||||
"""
|
"""
|
||||||
# prefer configured subject specific to key
|
# prefer configured subject specific to key
|
||||||
|
|
@ -664,6 +722,64 @@ class EmailHandler(GenericHandler): # pylint: disable=too-many-public-methods
|
||||||
default=self.universal_subject,
|
default=self.universal_subject,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_auto_subject_prefix(
|
||||||
|
self, key, default=None, fallback_key=None, setting=None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Returns the string to use for automatic subject prefix, as
|
||||||
|
determined by config. This is called by
|
||||||
|
:meth:`get_auto_subject()`.
|
||||||
|
|
||||||
|
Note that unlike the subject proper, the prefix is just a
|
||||||
|
normal string, not a template.
|
||||||
|
|
||||||
|
Example prefix is ``"[Wutta]"`` - trailing space will be added
|
||||||
|
automatically when applying the prefix to a message subject.
|
||||||
|
|
||||||
|
:param key: The :term:`email key` requested.
|
||||||
|
|
||||||
|
:param default: Default prefix to use if none is configured.
|
||||||
|
|
||||||
|
:param fallback_key: Optional fallback :term:`email key` to
|
||||||
|
use for config lookup, if nothing is found for ``key``.
|
||||||
|
|
||||||
|
:param setting: Optional :class:`EmailSetting` class or
|
||||||
|
instance. This may be used to determine the "default"
|
||||||
|
prefix if none is configured. You can specify this as an
|
||||||
|
optimization; otherwise it will be fetched if needed via
|
||||||
|
:meth:`get_email_setting()`.
|
||||||
|
|
||||||
|
:returns: Final subject prefix string
|
||||||
|
"""
|
||||||
|
|
||||||
|
# prefer configured prefix specific to key
|
||||||
|
if prefix := self.config.get(f"{self.config.appname}.email.{key}.prefix"):
|
||||||
|
return prefix
|
||||||
|
|
||||||
|
# or use caller-specified default, if applicable
|
||||||
|
if default:
|
||||||
|
return default
|
||||||
|
|
||||||
|
# or use fallback key, if provided
|
||||||
|
if fallback_key:
|
||||||
|
if prefix := self.config.get(
|
||||||
|
f"{self.config.appname}.email.{fallback_key}.prefix"
|
||||||
|
):
|
||||||
|
return prefix
|
||||||
|
|
||||||
|
# or prefix from email setting, if defined
|
||||||
|
if not setting:
|
||||||
|
setting = self.get_email_setting(key)
|
||||||
|
if setting:
|
||||||
|
if prefix := setting.get_default_prefix():
|
||||||
|
return prefix
|
||||||
|
|
||||||
|
# fall back to global default
|
||||||
|
return self.config.get(
|
||||||
|
f"{self.config.appname}.email.default.prefix",
|
||||||
|
default=f"[{self.app.get_node_title()}]",
|
||||||
|
)
|
||||||
|
|
||||||
def get_auto_to(self, key):
|
def get_auto_to(self, key):
|
||||||
"""
|
"""
|
||||||
Returns automatic :attr:`~wuttjamaican.email.Message.to`
|
Returns automatic :attr:`~wuttjamaican.email.Message.to`
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,17 @@ class TestEmailSetting(ConfigTestCase):
|
||||||
setting = MockSetting(self.config)
|
setting = MockSetting(self.config)
|
||||||
self.assertEqual(setting.get_description(), "this should be a good test")
|
self.assertEqual(setting.get_description(), "this should be a good test")
|
||||||
|
|
||||||
|
def test_get_default_prefix(self):
|
||||||
|
|
||||||
|
# empty by default
|
||||||
|
setting = mod.EmailSetting(self.config)
|
||||||
|
self.assertIsNone(setting.default_prefix)
|
||||||
|
self.assertIsNone(setting.get_default_prefix())
|
||||||
|
|
||||||
|
# but can override
|
||||||
|
setting.default_prefix = "[foo]"
|
||||||
|
self.assertEqual(setting.get_default_prefix(), "[foo]")
|
||||||
|
|
||||||
def test_sample_data(self):
|
def test_sample_data(self):
|
||||||
setting = mod.EmailSetting(self.config)
|
setting = mod.EmailSetting(self.config)
|
||||||
self.assertEqual(setting.sample_data(), {})
|
self.assertEqual(setting.sample_data(), {})
|
||||||
|
|
@ -136,6 +147,7 @@ class TestMessage(FileTestCase):
|
||||||
|
|
||||||
class mock_foo(mod.EmailSetting):
|
class mock_foo(mod.EmailSetting):
|
||||||
default_subject = "MOCK FOO!"
|
default_subject = "MOCK FOO!"
|
||||||
|
default_prefix = "[mock_foo]"
|
||||||
|
|
||||||
def sample_data(self):
|
def sample_data(self):
|
||||||
return {"foo": "mock"}
|
return {"foo": "mock"}
|
||||||
|
|
@ -253,7 +265,7 @@ class TestEmailHandler(ConfigTestCase):
|
||||||
self.assertIsInstance(msg, mod.Message)
|
self.assertIsInstance(msg, mod.Message)
|
||||||
self.assertEqual(msg.key, "foo")
|
self.assertEqual(msg.key, "foo")
|
||||||
self.assertEqual(msg.sender, "root@localhost")
|
self.assertEqual(msg.sender, "root@localhost")
|
||||||
self.assertEqual(msg.subject, "Automated message")
|
self.assertEqual(msg.subject, "[WuttJamaican] Automated message")
|
||||||
self.assertEqual(msg.to, [])
|
self.assertEqual(msg.to, [])
|
||||||
self.assertEqual(msg.cc, [])
|
self.assertEqual(msg.cc, [])
|
||||||
self.assertEqual(msg.bcc, [])
|
self.assertEqual(msg.bcc, [])
|
||||||
|
|
@ -270,7 +282,7 @@ class TestEmailHandler(ConfigTestCase):
|
||||||
self.assertIsInstance(msg, mod.Message)
|
self.assertIsInstance(msg, mod.Message)
|
||||||
self.assertEqual(msg.key, "foo")
|
self.assertEqual(msg.key, "foo")
|
||||||
self.assertEqual(msg.sender, "bob@example.com")
|
self.assertEqual(msg.sender, "bob@example.com")
|
||||||
self.assertEqual(msg.subject, "Attention required")
|
self.assertEqual(msg.subject, "[WuttJamaican] Attention required")
|
||||||
self.assertEqual(msg.to, [])
|
self.assertEqual(msg.to, [])
|
||||||
self.assertEqual(msg.cc, [])
|
self.assertEqual(msg.cc, [])
|
||||||
self.assertEqual(msg.bcc, [])
|
self.assertEqual(msg.bcc, [])
|
||||||
|
|
@ -287,7 +299,7 @@ class TestEmailHandler(ConfigTestCase):
|
||||||
msg = handler.make_auto_message("test_foo")
|
msg = handler.make_auto_message("test_foo")
|
||||||
self.assertEqual(msg.key, "test_foo")
|
self.assertEqual(msg.key, "test_foo")
|
||||||
self.assertEqual(msg.sender, "bob@example.com")
|
self.assertEqual(msg.sender, "bob@example.com")
|
||||||
self.assertEqual(msg.subject, "hello foo")
|
self.assertEqual(msg.subject, "[WuttJamaican] hello foo")
|
||||||
self.assertEqual(msg.to, ["sally@example.com"])
|
self.assertEqual(msg.to, ["sally@example.com"])
|
||||||
self.assertEqual(msg.cc, [])
|
self.assertEqual(msg.cc, [])
|
||||||
self.assertEqual(msg.bcc, [])
|
self.assertEqual(msg.bcc, [])
|
||||||
|
|
@ -311,7 +323,12 @@ class TestEmailHandler(ConfigTestCase):
|
||||||
get_auto_subject.assert_not_called()
|
get_auto_subject.assert_not_called()
|
||||||
msg = handler.make_auto_message("foo")
|
msg = handler.make_auto_message("foo")
|
||||||
get_auto_subject.assert_called_once_with(
|
get_auto_subject.assert_called_once_with(
|
||||||
"foo", {}, default=None, fallback_key=None
|
"foo",
|
||||||
|
{},
|
||||||
|
default=None,
|
||||||
|
prefix=True,
|
||||||
|
default_prefix=None,
|
||||||
|
fallback_key=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# to
|
# to
|
||||||
|
|
@ -421,36 +438,80 @@ class TestEmailHandler(ConfigTestCase):
|
||||||
)
|
)
|
||||||
self.assertEqual(template, "whatever is clever")
|
self.assertEqual(template, "whatever is clever")
|
||||||
|
|
||||||
|
def test_get_auto_subject_prefix(self):
|
||||||
|
handler = self.make_handler()
|
||||||
|
|
||||||
|
# global default
|
||||||
|
prefix = handler.get_auto_subject_prefix("foo")
|
||||||
|
self.assertEqual(prefix, "[WuttJamaican]")
|
||||||
|
|
||||||
|
# can configure alternate global default
|
||||||
|
self.config.setdefault("wutta.email.default.prefix", "[bar]")
|
||||||
|
prefix = handler.get_auto_subject_prefix("foo")
|
||||||
|
self.assertEqual(prefix, "[bar]")
|
||||||
|
|
||||||
|
# can configure just for key
|
||||||
|
self.config.setdefault("wutta.email.foo.prefix", "[foo]")
|
||||||
|
prefix = handler.get_auto_subject_prefix("foo")
|
||||||
|
self.assertEqual(prefix, "[foo]")
|
||||||
|
|
||||||
|
# can configure via fallback_key
|
||||||
|
self.config.setdefault("wutta.email.bar.prefix", "[baz]")
|
||||||
|
prefix = handler.get_auto_subject_prefix("foofoo", fallback_key="bar")
|
||||||
|
self.assertEqual(prefix, "[baz]")
|
||||||
|
|
||||||
|
# EmailSetting can provide default prefix
|
||||||
|
providers = {
|
||||||
|
"wuttatest": MagicMock(email_modules=["tests.test_email"]),
|
||||||
|
}
|
||||||
|
with patch.object(self.app, "providers", new=providers):
|
||||||
|
handler = self.make_handler()
|
||||||
|
prefix = handler.get_auto_subject_prefix("mock_foo")
|
||||||
|
self.assertEqual(prefix, "[mock_foo]")
|
||||||
|
|
||||||
|
# or caller can provide default
|
||||||
|
prefix = handler.get_auto_subject_prefix("mock_foo", default="[zzz]")
|
||||||
|
self.assertEqual(prefix, "[zzz]")
|
||||||
|
|
||||||
def test_get_auto_subject(self):
|
def test_get_auto_subject(self):
|
||||||
handler = self.make_handler()
|
handler = self.make_handler()
|
||||||
|
|
||||||
# global default
|
# global default
|
||||||
subject = handler.get_auto_subject("foo")
|
subject = handler.get_auto_subject("foo")
|
||||||
self.assertEqual(subject, "Automated message")
|
self.assertEqual(subject, "[WuttJamaican] Automated message")
|
||||||
|
|
||||||
# can configure alternate global default
|
# can configure alternate global default
|
||||||
self.config.setdefault("wutta.email.default.subject", "Wutta Message")
|
self.config.setdefault("wutta.email.default.subject", "Wutta Message")
|
||||||
subject = handler.get_auto_subject("foo")
|
subject = handler.get_auto_subject("foo")
|
||||||
self.assertEqual(subject, "Wutta Message")
|
self.assertEqual(subject, "[WuttJamaican] Wutta Message")
|
||||||
|
|
||||||
# caller can provide default subject
|
# caller can provide default subject
|
||||||
subject = handler.get_auto_subject("foo", default="whatever is clever")
|
subject = handler.get_auto_subject("foo", default="whatever is clever")
|
||||||
self.assertEqual(subject, "whatever is clever")
|
self.assertEqual(subject, "[WuttJamaican] whatever is clever")
|
||||||
|
|
||||||
# can configure just for key
|
# can configure just for key
|
||||||
self.config.setdefault("wutta.email.foo.subject", "Foo Message")
|
self.config.setdefault("wutta.email.foo.subject", "Foo Message")
|
||||||
subject = handler.get_auto_subject("foo")
|
subject = handler.get_auto_subject("foo")
|
||||||
self.assertEqual(subject, "Foo Message")
|
self.assertEqual(subject, "[WuttJamaican] Foo Message")
|
||||||
|
|
||||||
# proper template is rendered
|
# proper template is rendered..
|
||||||
self.config.setdefault("wutta.email.bar.subject", "${foo} Message")
|
self.config.setdefault("wutta.email.bar.subject", "${foo} Message")
|
||||||
subject = handler.get_auto_subject("bar", {"foo": "FOO"})
|
subject = handler.get_auto_subject("bar", {"foo": "FOO"})
|
||||||
self.assertEqual(subject, "FOO Message")
|
self.assertEqual(subject, "[WuttJamaican] FOO Message")
|
||||||
|
|
||||||
# unless we ask it not to
|
# ..unless we ask it not to
|
||||||
subject = handler.get_auto_subject("bar", {"foo": "FOO"}, rendered=False)
|
subject = handler.get_auto_subject("bar", {"foo": "FOO"}, rendered=False)
|
||||||
|
# nb. no prefix for unrendered template
|
||||||
self.assertEqual(subject, "${foo} Message")
|
self.assertEqual(subject, "${foo} Message")
|
||||||
|
|
||||||
|
# now suppress/override the prefix
|
||||||
|
subject = handler.get_auto_subject("foo")
|
||||||
|
self.assertEqual(subject, "[WuttJamaican] Foo Message")
|
||||||
|
subject = handler.get_auto_subject("foo", prefix=False)
|
||||||
|
self.assertEqual(subject, "Foo Message")
|
||||||
|
subject = handler.get_auto_subject("foo", default_prefix="[foo]")
|
||||||
|
self.assertEqual(subject, "[foo] Foo Message")
|
||||||
|
|
||||||
def test_get_auto_recips(self):
|
def test_get_auto_recips(self):
|
||||||
handler = self.make_handler()
|
handler = self.make_handler()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue