Changeset - 4c78223eb95e
[Not reviewed]
0 2 0
Lance Edgar (lance) - 5 years ago 2020-02-07 16:21:14
lance@edbob.org
Add new `ProblemReportEmail` base class, for simpler email previews

just so some of the template context can be provided automatically. although,
this *is* a bit heavy currently - should let caller pass in email handler etc.
2 files changed with 48 insertions and 2 deletions:
0 comments (0 inline, 0 general)
rattail/emails.py
Show inline comments
 
# -*- coding: utf-8; -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2019 Lance Edgar
 
#  Copyright © 2010-2020 Lance Edgar
 
#
 
#  This file is part of Rattail.
 
#
 
#  Rattail is free software: you can redistribute it and/or modify it under the
 
#  terms of the GNU General Public License as published by the Free Software
 
#  Foundation, either version 3 of the License, or (at your option) any later
 
#  version.
 
#
 
#  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
 
#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 
#  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 
#  details.
 
#
 
#  You should have received a copy of the GNU General Public License along with
 
#  Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 
"""
 
Common email config objects
 
"""
 

	
 
from __future__ import unicode_literals, absolute_import
 

	
 
import sys
 
import socket
 
from traceback import format_exception
 

	
 
import six
 

	
 
from rattail.mail import Email
 
from rattail.util import load_object
 
from rattail.core import Object
 
from rattail.time import make_utc, localtime
 
from rattail.problems import ProblemReport, get_problem_report_handler
 

	
 

	
 
class ProblemReportEmail(Email):
 
    """
 
    Base class for all "problem report" emails
 
    """
 
    abstract = True
 

	
 
    def obtain_sample_data(self, request):
 
        data = self.sample_data(request)
 
        handler = get_problem_report_handler(self.config)
 

	
 
        if 'report' not in data:
 
            reports = handler.get_all_problem_reports()
 
            email_key = self.__class__.__name__
 
            for report in reports:
 
                if report.email_key == email_key:
 
                    data['report'] = report(self.config)
 
                    break
 

	
 
            if 'report' not in data:
 
                report = ProblemReport(self.config)
 
                report.problem_title = "Generic Title (problem report not found)"
 
                data['report'] = report
 

	
 
        if 'system_title' not in data:
 
            system_key = data['report'].system_key or 'rattail'
 
            data['system_title'] = handler.get_system_title(system_key)
 

	
 
        return data
 

	
 

	
 
class datasync_error_watcher_get_changes(Email):
 
    """
 
    When any datasync watcher thread encounters an error trying to get changes,
 
    this email is sent out.
 
    """
 
    default_subject = "Watcher failed to get changes"
 

	
 
    def sample_data(self, request):
 
        from rattail.datasync import DataSyncWatcher
 
        try:
 
            raise RuntimeError("Fake error for preview")
 
        except:
 
            exc_type, exc, traceback = sys.exc_info()
 
        watcher = DataSyncWatcher(self.config, 'test')
 
        watcher.consumes_self = True
 
        return {
 
            'watcher': watcher,
 
            'error': exc,
 
            'traceback': ''.join(format_exception(exc_type, exc, traceback)).strip(),
 
            'datasync_url': '/datasyncchanges',
 
            'attempts': 2,
 
        }
 

	
 

	
 
class datasync_error_consumer_process_changes(Email):
 
    """
 
    When any datasync consumer thread encounters an error trying to process
 
    changes, this email is sent out.
 
    """
 
    default_subject = "Consumer failed to process changes"
 

	
 
    def sample_data(self, request):
 
        from rattail.datasync import DataSyncWatcher, DataSyncConsumer
 

	
 
        try:
 
            raise RuntimeError("Fake error for preview")
 
        except:
 
            exc_type, exc, traceback = sys.exc_info()
 

	
 
        watcher = DataSyncWatcher(self.config, 'testwatcher')
 
        consumer = DataSyncConsumer(self.config, 'testconsumer')
 
        return {
 
            'watcher': watcher,
 
            'consumer': consumer,
 
            'error': exc,
 
            'attempts': 2,
rattail/mail.py
Show inline comments
 
# -*- coding: utf-8; -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2018 Lance Edgar
 
#  Copyright © 2010-2020 Lance Edgar
 
#
 
#  This file is part of Rattail.
 
#
 
#  Rattail is free software: you can redistribute it and/or modify it under the
 
#  terms of the GNU General Public License as published by the Free Software
 
#  Foundation, either version 3 of the License, or (at your option) any later
 
#  version.
 
#
 
#  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
 
#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 
#  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 
#  details.
 
#
 
#  You should have received a copy of the GNU General Public License along with
 
#  Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 
"""
 
Email Framework
 
"""
 

	
 
from __future__ import unicode_literals, absolute_import
 

	
 
import os
 
import smtplib
 
import logging
 
from email.charset import Charset
 
from email.message import Message
 
from email.mime.multipart import MIMEMultipart
 
from email.mime.application import MIMEApplication
 
from email.mime.text import MIMEText
 

	
 
import six
 

	
 
from mako.template import Template
 
from mako.lookup import TemplateLookup
 
from mako.exceptions import TopLevelLookupException
 

	
 
from rattail import exceptions
 
from rattail.core import UNSPECIFIED
 
from rattail.files import resource_path
 
from rattail.util import import_module_path
 
from rattail.time import localtime, make_utc
 

	
 

	
 
# NOTE: this bit of magic was stolen from Django
 
# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
 
# some spam filters.
 
@@ -264,97 +264,112 @@ class EmailHandler(object):
 
                log.debug("login() result is: %s", repr(result))
 

	
 
            result = session.sendmail(msg['From'], recips, msg.as_string())
 
            log.debug("sendmail() result is: %s", repr(result))
 
            session.quit()
 
            return True
 

	
 
        log.debug("config says no emails for '%s', but would have sent one to: %s", email.key, recips)
 
        return False
 

	
 

	
 
class Email(object):
 
    # Note: The docstring of an email is leveraged by code, hence this odd one.
 
    """
 
    (This email has no description.)
 
    """
 
    key = None
 
    fallback_key = None
 
    abstract = False
 
    default_prefix = "[rattail]"
 
    default_subject = "Automated message"
 

	
 
    # Whether or not the email's :attr:`to` attribute is dynamically determined
 
    # at run-time, i.e. via some logic other than typical reading from config.
 
    dynamic_to = False
 
    dynamic_to_help = None
 

	
 
    def __init__(self, config, key=None, fallback_key=None, default_subject=None):
 
        self.config = config
 
        self.enum = config.get_enum()
 

	
 
        if key:
 
            self.key = key
 
        elif not self.key:
 
            self.key = self.__class__.__name__
 
            if self.key == 'Email':
 
                raise exceptions.ConfigurationError("Email instance has no key: {0}".format(repr(self)))
 

	
 
        if fallback_key:
 
            self.fallback_key = fallback_key
 
        if default_subject:
 
            self.default_subject = default_subject
 

	
 
        templates = config.getlist('rattail.mail', 'templates')
 
        if templates:
 
            templates = [resource_path(p) for p in templates]
 
        self.templates = TemplateLookup(directories=templates)
 

	
 
    def obtain_sample_data(self, request):
 
        """
 
        This method is responsible for obtaining the full set of sample data,
 
        to be used as context when generating a preview for the email.
 

	
 
        Note, you normally should not override this method!  Please see also
 
        the :meth:`sample_data()` method.
 
        """
 
        return self.sample_data(request)
 

	
 
    def sample_data(self, request):
 
        """
 
        This method can return a dict of sample data, to be used as context
 
        when generating a preview for the email.  Subclasses are welcome to
 
        override this method.
 
        """
 
        return {}
 

	
 
    def get_enabled(self):
 
        """
 
        Get the enabled flag for the email's message type.
 
        """
 
        enabled = self.config.getbool('rattail.mail', '{0}.enabled'.format(self.key))
 
        if enabled is not None:
 
            return enabled
 
        enabled = self.config.getbool('rattail.mail', 'default.enabled')
 
        if enabled is not None:
 
            return enabled
 
        return self.config.getbool('rattail.mail', 'send_emails', default=True)
 

	
 
    def get_sender(self):
 
        """
 
        Returns the value for the message's ``From:`` header.
 

	
 
        :rtype: str
 
        """
 
        sender = self.config.get('rattail.mail', '{0}.from'.format(self.key))
 
        if not sender:
 
            sender = self.config.get('rattail.mail', 'default.from')
 
            if not sender:
 
                raise exceptions.SenderNotFound(self.key)
 
        return sender
 

	
 
    def get_replyto(self):
 
        """
 
        Get the Reply-To address for the message.
 
        """
 
        replyto = self.config.get('rattail.mail', '{0}.replyto'.format(self.key))
 
        if not replyto:
 
            replyto = self.config.get('rattail.mail', 'default.replyto')
 
        return replyto
 

	
 
    def get_recips(self, type_='to'):
 
        """
 
        Returns a list of recipients of the given type for the message.
 

	
 
        :param type_: Must be one of: ``('to', 'cc', 'bcc')``.
 

	
 
        :rtype: list
 
        """
 
        try:
 
            if type_.lower() not in ('to', 'cc', 'bcc'):
 
                raise Exception
 
        except:
0 comments (0 inline, 0 general)