reworking of email sending

Emails now send at 3 different stages.
Now we have two different types of email templates, one html,
one plaintext.
The templates, and reply email can be set actionview, per stage.

Change-Id: I3a37959c547232c8f0df60a69953cc4ac7441d8e
This commit is contained in:
adriant 2015-07-28 12:33:17 +12:00
parent ee43eb1c0c
commit 93c9806990
11 changed files with 256 additions and 56 deletions

View File

@ -1,3 +1,3 @@
include README.md
graft stacktask/*/templates
include stacktask/*/templates/*

View File

@ -48,19 +48,65 @@ DEFAULT_REGION: RegionOne
# Additonal actions for views:
# - The order of the actions matters. These will run after the default action,
# in the given order.
API_ACTIONS:
ACTIONVIEW_SETTINGS:
CreateProject:
- AddAdminToProject
- DefaultProjectResources
actions:
- AddAdminToProject
- DefaultProjectResources
emails:
initial:
subject: Initial Confirmation
reply: no-reply@example.com
template: initial.txt
html_template: initial.txt
# If the related actions 'can' send a token,
# this field should here.
token:
subject: Your Token
reply: no-reply@example.com
template: token.txt
html_template: token.txt
completed:
subject: Registration completed
reply: no-reply@example.com
template: completed.txt
html_template: completed.txt
AttachUser:
emails:
# To not send this email, set the value to null,
# or don't have the field there at all.
initial: null
token:
subject: Your Token
reply: no-reply@example.com
template: token.txt
html_template: token.txt
completed:
subject: Registration completed
reply: no-reply@example.com
template: completed.txt
html_template: completed.txt
ResetPassword:
emails:
token:
subject: Your Token
reply: no-reply@example.com
template: token.txt
html_template: token.txt
completed:
subject: Registration completed
reply: no-reply@example.com
template: completed.txt
html_template: completed.txt
# Action settings:
ACTION_SETTINGS:
DefaultProjectResources:
"RegionOne":
RegionOne:
network_name: somenetwork
subnet_name: somesubnet
router_name: somerouter
public_network: 83559fa7-0a67-4716-94b9-10596e3ed1e6
public_network: 3cb50d61-5bce-4c03-96e6-8e262e12bb35
DNS_NAMESERVERS:
- 193.168.1.2
- 193.168.1.3

View File

@ -4,7 +4,7 @@ from setuptools import setup, find_packages
setup(
name='stacktask',
version='0.1.0a3',
version='0.1.0a4',
description='A user registration service for openstack.',
long_description=(
'A registration service to sit alongside keystone and ' +

View File

@ -28,6 +28,7 @@ class Migration(migrations.Migration):
('uuid', models.CharField(default=stacktask.api_v1.models.hex_uuid, max_length=200, serialize=False, primary_key=True)),
('reg_ip', models.GenericIPAddressField()),
('keystone_user', jsonfield.fields.JSONField(default={})),
('action_view', models.CharField(max_length=200)),
('action_notes', jsonfield.fields.JSONField(default={})),
('approved', models.BooleanField(default=False)),
('completed', models.BooleanField(default=False)),

View File

@ -34,6 +34,9 @@ class Registration(models.Model):
reg_ip = models.GenericIPAddressField()
keystone_user = JSONField(default={})
# which ActionView initiated this
action_view = models.CharField(max_length=200)
# Effectively a log of what the actions are doing.
action_notes = JSONField(default={})

View File

@ -0,0 +1,11 @@
Hello,
Your registration has been completed.
The actions you had requested are:
{% for action in actions %}
- {{ action }}
{% endfor %}
Thank you for using our service.

View File

@ -0,0 +1,13 @@
Hello,
Your registration is in our system and now waiting approval.
The actions you had requested are:
{% for action in actions %}
- {{ action }}
{% endfor %}
Once someone has approved your registration you will be emailed an update.
Thank you for using our service.

View File

@ -80,44 +80,52 @@ def create_token(registration):
return token
def email_token(registration, token):
def send_email(registration, email_conf, token=None):
if email_conf:
template = loader.get_template(email_conf['template'])
html_template = loader.get_template(email_conf['html_template'])
emails = set()
actions = []
for action in registration.actions:
act = action.get_action()
if act.need_token:
emails.add(act.token_email())
actions.append(unicode(act))
emails = set()
actions = []
for action in registration.actions:
act = action.get_action()
email = act.get_email()
if email:
emails.add(email)
actions.append(unicode(act))
if len(emails) > 1:
notes = {
'notes':
(("Error: Unable to send token, More than one email for" +
" registration: %s") % registration.uuid)
}
create_notification(registration, notes)
# TODO(adriant): raise some error?
# and surround calls to this function with try/except
if len(emails) > 1:
notes = {
'notes':
(("Error: Unable to send token, More than one email for" +
" registration: %s") % registration.uuid)
}
create_notification(registration, notes)
return
# TODO(adriant): raise some error?
# and surround calls to this function with try/except
context = {'actions': actions, 'token': token.token}
if token:
context = {'registration': registration, 'actions': actions,
'token': token.token}
else:
context = {'registration': registration, 'actions': actions}
email_template = loader.get_template("token.txt")
try:
message = email_template.render(Context(context))
send_mail(
'Your token', message, 'no-reply@example.com',
[emails.pop()], fail_silently=False)
except SMTPException as e:
notes = {
'notes':
("Error: '%s' while emailing token for registration: %s" %
(e, registration.uuid))
}
create_notification(registration, notes)
# TODO(adriant): raise some error?
# and surround calls to this function with try/except
try:
message = template.render(Context(context))
html_message = html_template.render(Context(context))
send_mail(
email_conf['subject'], message, email_conf['reply'],
[emails.pop()], fail_silently=False, html_message=html_message)
except SMTPException as e:
notes = {
'notes':
("Error: '%s' while emailing token for registration: %s" %
(e, registration.uuid))
}
create_notification(registration, notes)
# TODO(adriant): raise some error?
# and surround calls to this function with try/except
def create_notification(registration, notes):
@ -379,8 +387,38 @@ class RegistrationDetail(APIViewWithLogger):
registration.save()
if need_token:
token = create_token(registration)
email_token(registration, token)
return Response({'notes': ['created token']}, status=200)
try:
class_conf = settings.ACTIONVIEW_SETTINGS[
registration.action_view]
# will throw a key error if the token template has not
# been specified
email_conf = class_conf['emails']['token']
send_email(registration, email_conf, token)
return Response({'notes': ['created token']},
status=200)
except KeyError as e:
notes = {
'errors':
[("Error: '%s' while sending " +
"token. See registration " +
"itself for details.") % e],
'registration': registration.uuid
}
create_notification(registration, notes)
import traceback
trace = traceback.format_exc()
self.logger.critical(("(%s) - Exception escaped!" +
" %s\n Trace: \n%s") %
(timezone.now(), e, trace))
response_dict = {
'errors':
["Error: Something went wrong on the " +
"server. It will be looked into shortly."]
}
return Response(response_dict, status=500)
else:
for action in actions:
try:
@ -406,6 +444,14 @@ class RegistrationDetail(APIViewWithLogger):
registration.completed = True
registration.completed_on = timezone.now()
registration.save()
# Sending confirmation email:
class_conf = settings.ACTIONVIEW_SETTINGS.get(
registration.action_view, {})
email_conf = class_conf.get(
'emails', {}).get('completed', None)
send_email(registration, email_conf)
return Response(
{'notes': "Registration completed successfully."},
status=200)
@ -458,7 +504,36 @@ class TokenList(APIViewWithLogger):
token.delete()
token = create_token(registration)
email_token(registration, token)
try:
class_conf = settings.ACTIONVIEW_SETTINGS[
registration.action_view]
# will throw a key error if the token template has not
# been specified
email_conf = class_conf['emails']['token']
send_email(registration, email_conf, token)
except KeyError as e:
notes = {
'errors':
[("Error: '%s' while sending " +
"token. See registration " +
"itself for details.") % e],
'registration': registration.uuid
}
create_notification(registration, notes)
import traceback
trace = traceback.format_exc()
self.logger.critical(("(%s) - Exception escaped!" +
" %s\n Trace: \n%s") %
(timezone.now(), e, trace))
response_dict = {
'errors':
["Error: Something went wrong on the " +
"server. It will be looked into shortly."]
}
return Response(response_dict, status=500)
return Response(
{'notes': ['Token reissued.']}, status=200)
@ -584,6 +659,13 @@ class TokenDetail(APIViewWithLogger):
token.registration.save()
token.delete()
# Sending confirmation email:
class_conf = settings.ACTIONVIEW_SETTINGS.get(
token.registration.action_view, {})
email_conf = class_conf.get(
'emails', {}).get('completed', None)
send_email(token.registration, email_conf)
return Response(
{'notes': "Token submitted successfully."},
status=200)
@ -630,9 +712,12 @@ class ActionView(APIViewWithLogger):
function on all the actions.
"""
class_conf = settings.ACTIONVIEW_SETTINGS.get(self.__class__.__name__,
{})
actions = [self.default_action, ]
actions += settings.API_ACTIONS.get(self.__class__.__name__, [])
actions += class_conf.get('actions', [])
act_list = []
@ -658,7 +743,8 @@ class ActionView(APIViewWithLogger):
keystone_user = request.keystone_user
registration = Registration.objects.create(
reg_ip=ip_addr, keystone_user=keystone_user)
reg_ip=ip_addr, keystone_user=keystone_user,
action_view=self.__class__.__name__)
registration.save()
for i, act in enumerate(act_list):
@ -695,6 +781,10 @@ class ActionView(APIViewWithLogger):
}
return response_dict
# send initial conformation email:
email_conf = class_conf.get('emails', {}).get('initial', None)
send_email(registration, email_conf)
return {'registration': registration}
else:
errors = {}
@ -759,8 +849,38 @@ class ActionView(APIViewWithLogger):
if valid:
if need_token:
token = create_token(registration)
email_token(registration, token)
return Response({'notes': ['created token']}, status=200)
try:
class_conf = settings.ACTIONVIEW_SETTINGS[
self.__class__.__name__]
# will throw a key error if the token template has not
# been specified
email_conf = class_conf['emails']['token']
send_email(registration, email_conf, token)
return Response({'notes': ['created token']},
status=200)
except KeyError as e:
notes = {
'errors':
[("Error: '%s' while sending " +
"token. See registration " +
"itself for details.") % e],
'registration': registration.uuid
}
create_notification(registration, notes)
import traceback
trace = traceback.format_exc()
self.logger.critical(("(%s) - Exception escaped!" +
" %s\n Trace: \n%s") %
(timezone.now(), e, trace))
response_dict = {
'errors':
["Error: Something went wrong on the " +
"server. It will be looked into shortly."]
}
return Response(response_dict, status=500)
else:
for action in actions:
try:
@ -791,6 +911,13 @@ class ActionView(APIViewWithLogger):
registration.completed = True
registration.completed_on = timezone.now()
registration.save()
# Sending confirmation email:
class_conf = settings.ACTIONVIEW_SETTINGS.get(
self.__class__.__name__, {})
email_conf = class_conf.get(
'emails', {}).get('completed', None)
send_email(registration, email_conf)
return Response(
{'notes': "Registration completed successfully."},
status=200)

View File

@ -128,11 +128,11 @@ class BaseAction(object):
def need_token(self):
return self.action.need_token
def token_email(self):
return self._token_email()
def get_email(self):
return self._get_email()
def _token_email(self):
raise NotImplementedError
def _get_email(self):
return None
def get_cache(self, key):
return self.action.cache.get(key, None)
@ -191,7 +191,7 @@ class UserAction(BaseAction):
else:
super(UserAction, self).__init__(*args, **kwargs)
def _token_email(self):
def _get_email(self):
return self.email

View File

@ -59,7 +59,7 @@ class IdentityManager(object):
return role
def get_roles(self, user, project):
return self.ks.roles.roles_for_user(user, tenant=project)
return self.ks_client.roles.roles_for_user(user, tenant=project)
def add_user_role(self, user, role, project_id):
self.ks_client.roles.add_user_role(user, role, project_id)

View File

@ -118,8 +118,7 @@ DEFAULT_REGION = CONFIG['DEFAULT_REGION']
# Additonal actions for views:
# - The order of the actions matters. These will run after the default action,
# in the given order.
API_ACTIONS = {'CreateProject': ['AddAdminToProject',
'DefaultProjectResources']}
ACTIONVIEW_SETTINGS = CONFIG['ACTIONVIEW_SETTINGS']
ACTION_SETTINGS = CONFIG['ACTION_SETTINGS']