diff --git a/initpyr.py b/initpyr.py index ae75be6..a0013ae 100755 --- a/initpyr.py +++ b/initpyr.py @@ -152,6 +152,7 @@ def perform_installs(): subprocess.call(["bin/easy_install", "gunicorn"]) subprocess.call(["bin/easy_install", "redis"]) subprocess.call(["bin/easy_install", "wtforms"]) + subprocess.call(["bin/easy_install", "pyramid_mailer"]) if options.database_type is "postgresql": subprocess.call(["bin/easy_install", "psycopg2"]) @@ -193,15 +194,15 @@ def setup_dotini(): # Add template if it is in the yaml file #if settings["template"] != None: - substitute_in_file(developmentini, "pyramid.includes =", "pyramid.includes =\n pyramid_mako") - substitute_in_file(productionini, "pyramid.includes =", "pyramid.includes =\n pyramid_mako") + substitute_in_file(developmentini, "pyramid.includes =", "pyramid.includes =\n pyramid_mailer\n pyramid_mako") + substitute_in_file(productionini, "pyramid.includes =", "pyramid.includes =\n pyramid_mailer\n pyramid_mako") authsecret_orig = "sqlalchemy.url = sqlite:///%(here)s/" + options.project_name + ".sqlite" if options.database_type is "postgresql": authsecret_orig = "sqlalchemy.url = postgresql+psycopg2://PGUSERNAME:PGPASSWORD@localhost/" + options.project_name - authsecret_subst = authsecret_orig + "\n\nauth.secret=PLEASECHANGEME\n\nsession.secret = PLEASECHANGEMETOO\n\nemail.enable=true\nemail.from=change@me.com\nemail.host=sendmail" + authsecret_subst = authsecret_orig + "\n\nauth.secret=PLEASECHANGEME\n\nsession.secret = PLEASECHANGEMETOO\n\nmail.host=smtp.mandrillapp.com\nmail.username=YOURMANDRILLAPPUSERNAME\nmail.password=YOURMANDRILLAPPPASSWORD\nmail.port=587\nmail.ssl=False\nmail.default_sender=donotreply@YOURDOMAIN" substitute_in_file(developmentini, authsecret_orig, authsecret_subst) substitute_in_file(productionini, authsecret_orig, authsecret_subst) substitute_in_file(productionini, "[server:main]", "[server:main]\nunix_socket = %(here)s/" + unix_app_socket + "\nhost = localhost\nport = 80\n") diff --git a/initpyr.yaml b/initpyr.yaml index 759dc96..4ae889e 100644 --- a/initpyr.yaml +++ b/initpyr.yaml @@ -23,6 +23,7 @@ maininitpy: config.set_request_factory(RequestWithUserAttribute) config = build_routes(config) config.add_renderer('jsonp', JSONP(param_name='callback')) + config.registry['mailer'] = Mailer.from_settings(settings) mainimports: | from os.path import abspath from os.path import dirname @@ -42,6 +43,7 @@ maininitpy: from pyramid.security import ALL_PERMISSIONS from pyramid.session import SignedCookieSessionFactory + from pyramid_mailer.mailer import Mailer class RootFactory(object): __acl__ = [(Allow, Everyone, ALL_PERMISSIONS)] diff --git a/lib/msg.py b/lib/msg.py new file mode 100644 index 0000000..ae9aa59 --- /dev/null +++ b/lib/msg.py @@ -0,0 +1,186 @@ +"""Create and send messages to users + +""" +import logging +import os +import smtplib + +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from pyramid.settings import asbool +from pyramid_mailer.message import Message +import pyramid_mailer +from os import path +from pyramid.paster import bootstrap + + +LOG = logging.getLogger(__name__) + +# notification statuses +# might have pending, sent, failed +MSG_STATUS = { + 'pending': 0, + 'sent': 1, + 'failed': 2, + 'not_sent': 3, + 'error': 4, +} + +app_settings = bootstrap(path.join(path.dirname(path.dirname(path.dirname(__file__))), "development.ini"))['registry'].settings + +class Msg(object): + """This is a base email message we can then tweak""" + + def __init__(self, to, subject, settings): + """Start up a basic message""" + self.to = to + self.subject = subject + self.settings = settings + + self.from_addr = settings.get('email.from', None) + + # need ot setup/override in the extending classes + self.message_file = None + + def _get_message_body(self, template_file, message_data): + """Return the completed message template body + + """ + return "Test email message from bookie" + # lookup = config['pylons.app_globals'].mako_lookup + # template = lookup.get_template(template_file) + + # # template vars are a combo of the obj dict and the extra dict + # template_vars = {'data': message_data} + # return template.render(**template_vars) + + def send(self, message_data=None): + """Send the message with the given subject + + body can be sent as part of send() or it can be set to the object as + msg.body = "xxx" + + """ + + self.body = self._get_message_body(self.message_file, message_data) + + msg = MIMEMultipart('related') + msg['Subject'] = self.subject + msg['From'] = self.from_addr + + msg['To'] = self.to + + plain_text = MIMEText(self.body, 'plain', _charset="UTF-8") + msg.attach(plain_text) + + LOG.debug('msg: ' + repr(msg)) + + mailer = pyramid_mailer.mailer.Mailer.from_settings(app_settings) + message = Message(subject=msg['Subject'], + recipients=[msg['To']], + body=self.body) + + mailer.send_immediately(message, fail_silently=False) + return MSG_STATUS['sent'] + +class ReactivateMsg(Msg): + """Send an email for a reactivation email""" + + def _get_message_body(self, template_file, message_data): + """Return the completed message template body + + """ + return """ +Hello {username}: + +Please activate your app account by clicking on the following url: + +{url} + +--- +From Us""".format(**message_data) + # lookup = config['pylons.app_globals'].mako_lookup + # template = lookup.get_template(template_file) + + # # template vars are a combo of the obj dict and the extra dict + # template_vars = {'data': message_data} + # return template.render(**template_vars) + + +class InvitationMsg(Msg): + """Send an email that you've been invited to the system""" + def _get_message_body(self, template_file, message_data): + """Return the completed message template body + + """ + return """ +You've been invited to The Site! + +Please click the link below to activate your account. + +{0} + +--- +From Us""".format(message_data) + + +class ImportFailureMessage(Msg): + """Send an email that the import has failed.""" + + def _get_message_body(self, template_file, message_data): + """Build the email message body.""" + + msg = """ +The import for user {username} has failed to import. The path to the import +is: + +{file_path} + +Error: + +{exc} + +""".format(**message_data) + return msg + + +class UserImportFailureMessage(Msg): + """Send an email to the user their import has failed.""" + + def _get_message_body(self, template_file, message_data): + """Build the email message body.""" + + msg = """ +Your import has failed. The error is listed below. Please file a bug at +https://github.com/mitechie/bookie/issues if this error continues. You may +also join #bookie on freenode irc if you wish to aid in debugging the issue. +If the error pertains to a specific bookmark in your import file you might try +removing it and importing the file again. + +Error +---------- + +{exc} + +A copy of this error has been logged and will be looked at. + +--- +From Us""".format(**message_data) + return msg + + +class UserImportSuccessMessage(Msg): + """Send an email to the user after a successful import.""" + + def _get_message_body(self, template_file, message_data): + """Build the email message body.""" + + msg = """ +Your bookmark import is complete! We've begun processing your bookmarks to +load their page contents and fulltext index them. This process might take a +while if you have a large number of bookmarks. Check out your imported +bookmarks at https://bmark.us/{username}/recent. + +--- +From Us""".format(**message_data) + return msg diff --git a/msmtprc.sample b/msmtprc.sample deleted file mode 100644 index 73aa5e1..0000000 --- a/msmtprc.sample +++ /dev/null @@ -1,29 +0,0 @@ - -# rename this file to `.msmtprc` -# change the owner of this file to you -# set the mode of this file to 0600 -# copy this file to your $HOME - -# Use an external SMTP server with insecure authentication. -# (manually choose an insecure authentication method.) -# Note that the password contains blanks. - -defaults - -###################################################################### -# A sample configuration using Gmail -###################################################################### - -# account name is "gmail". -# You can select this account by using "-a gmail" in your command line. -account gmail -host smtp.gmail.com -tls on -tls_certcheck off -port 587 -auth login -from myaccount@gmail.com -user myaccount -password mypassword - -account default: gmail diff --git a/queue/tasks.py b/queue/tasks.py index b643a63..41f24b2 100644 --- a/queue/tasks.py +++ b/queue/tasks.py @@ -3,11 +3,6 @@ from ~~~PROJNAME~~~.queue.celery import celery import transaction -#from celery import Celery - -# from .celery import load_ini -# INI = load_ini() - import logging LOG = logging.getLogger(__name__) @@ -22,7 +17,7 @@ def email_signup_user(email, msg, settings, message_data): :param iid: import id we need to pull and work on """ - from ~~~PROJNAME~~~.lib.message import InvitationMsg + from ~~~PROJNAME~~~.lib.msg import InvitationMsg msg = InvitationMsg(email, msg, settings) status = msg.send(message_data) if status == 4: diff --git a/views/api.py b/views/api.py index 4c36f80..e95aa80 100644 --- a/views/api.py +++ b/views/api.py @@ -11,8 +11,8 @@ from ~~~PROJNAME~~~.queue import tasks from ~~~PROJNAME~~~.lib.access import api_auth from ~~~PROJNAME~~~.lib.applog import AuthLog -from ~~~PROJNAME~~~.lib.message import ReactivateMsg -from ~~~PROJNAME~~~.lib.message import InvitationMsg +from ~~~PROJNAME~~~.lib.msg import ReactivateMsg +from ~~~PROJNAME~~~.lib.msg import InvitationMsg #from ~~~PROJNAME~~~.lib.readable import ReadContent #from ~~~PROJNAME~~~.lib.tagcommands import Commander