Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: activation email theming with edx-ace #1310

Draft
wants to merge 6 commits into
base: appsembler/hawthorn/master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions common/djangoapps/student/message_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,38 @@ def __init__(self, *args, **kwargs):
super(PasswordReset, self).__init__(*args, **kwargs)

self.options['transactional'] = True


class AccountRecovery(BaseMessageType):
def __init__(self, *args, **kwargs):
super(AccountRecovery, self).__init__(*args, **kwargs)

self.options['transactional'] = True


class EmailChange(BaseMessageType):
def __init__(self, *args, **kwargs):
super(EmailChange, self).__init__(*args, **kwargs)

self.options['transactional'] = True


class EmailChangeConfirmation(BaseMessageType):
def __init__(self, *args, **kwargs):
super(EmailChangeConfirmation, self).__init__(*args, **kwargs)

self.options['transactional'] = True


class RecoveryEmailCreate(BaseMessageType):
def __init__(self, *args, **kwargs):
super(RecoveryEmailCreate, self).__init__(*args, **kwargs)

self.options['transactional'] = True


class AccountActivation(BaseMessageType):
def __init__(self, *args, **kwargs):
super(AccountActivation, self).__init__(*args, **kwargs)

self.options['transactional'] = True
44 changes: 30 additions & 14 deletions common/djangoapps/student/tasks.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,49 @@
"""
This file contains celery tasks for sending email
"""


import logging

from boto.exception import NoAuthHandlerFound
from celery.exceptions import MaxRetriesExceededError
from celery.task import task # pylint: disable=no-name-in-module, import-error
from celery.task import task
from django.conf import settings
from django.core import mail
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from edx_ace import ace
from edx_ace.errors import RecoverableChannelDeliveryError
from edx_ace.message import Message

from openedx.core.lib.celery.task_utils import emulate_http_request

log = logging.getLogger('edx.celery.task')


@task(bind=True)
def send_activation_email(self, subject, message, from_address, dest_addr):
def send_activation_email(self, msg_string, site_id, from_address):
"""
Sending an activation email to the user.
"""
msg = Message.from_string(msg_string)

max_retries = settings.RETRY_ACTIVATION_EMAIL_MAX_ATTEMPTS
retries = self.request.retries

msg.options['from_address'] = from_address

dest_addr = msg.recipient.email_address

user = User.objects.get(username=msg.recipient.username)

# Tahoe: `get_current_site()` don't work in celery tasks because there's no `request`.
# Getting the `site` from the caller instead.
site = Site.objects.get(pk=site_id)

try:
mail.send_mail(subject, message, from_address, [dest_addr], fail_silently=False)
# Log that the Activation Email has been sent to user without an exception
log.info("Activation Email has been sent to User {user_email}".format(
user_email=dest_addr
))
except NoAuthHandlerFound: # pylint: disable=broad-except
log.info('Retrying sending email to user {dest_addr}, attempt # {attempt} of {max_attempts}'. format(
with emulate_http_request(site=site, user=user):
ace.send(msg)
except RecoverableChannelDeliveryError:
log.info('Retrying sending email to user {dest_addr}, attempt # {attempt} of {max_attempts}'.format(
dest_addr=dest_addr,
attempt=retries,
max_attempts=max_retries
Expand All @@ -40,11 +57,10 @@ def send_activation_email(self, subject, message, from_address, dest_addr):
dest_addr,
exc_info=True
)
except Exception: # pylint: disable=bare-except
except Exception as e:
log.exception(
'Unable to send activation email to user from "%s" to "%s"',
from_address,
dest_addr,
exc_info=True
)
raise Exception
raise e
60 changes: 44 additions & 16 deletions common/djangoapps/student/views/management.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
from social_core.exceptions import AuthAlreadyAssociated, AuthException
from social_django import utils as social_utils
from xmodule.modulestore.django import modulestore
from edx_ace.recipient import Recipient

import openedx.core.djangoapps.external_auth.views
import third_party_auth
Expand All @@ -72,6 +73,7 @@
from openedx.core.djangolib.markup import HTML, Text
from student.cookies import set_logged_in_cookies
from student.forms import AccountCreationForm, PasswordResetFormNoActive, get_registration_extension_form
from student.message_types import AccountActivation, EmailChange, EmailChangeConfirmation, RecoveryEmailCreate
from student.helpers import (
DISABLE_UNENROLL_CERT_STATES,
AccountValidationError,
Expand Down Expand Up @@ -247,32 +249,58 @@ def register_user(request, extra_context=None):

return render_to_response('register.html', context)

def compose_activation_email(root_url, user, user_registration=None, route_enabled=False, profile_name=''):
"""
Construct all the required params for the activation email
through celery task
"""
if user_registration is None:
user_registration = Registration.objects.get(user=user)

message_context = generate_activation_email_context(user, user_registration)
message_context.update({
'confirm_activation_link': '{root_url}/activate/{activation_key}'.format(
root_url=root_url,
activation_key=message_context['key']
),
'route_enabled': route_enabled,
'routed_user': user.username,
'routed_user_email': user.email,
'routed_profile_name': profile_name,
})

if route_enabled:
dest_addr = settings.FEATURES['REROUTE_ACTIVATION_EMAIL']
else:
dest_addr = user.email

msg = AccountActivation().personalize(
recipient=Recipient(user.username, dest_addr),
language=preferences_api.get_user_preference(user, LANGUAGE_KEY),
user_context=message_context,
)

return msg

def compose_and_send_activation_email(user, profile, user_registration=None):
"""
Construct all the required params and send the activation email
through celery task

Arguments:
user: current logged-in user
profile: profile object of the current logged-in user
user_registration: registration of the current logged-in user
"""
dest_addr = user.email
if user_registration is None:
user_registration = Registration.objects.get(user=user)
context = generate_activation_email_context(user, user_registration)
subject = render_to_string('emails/activation_email_subject.txt', context)
# Email subject *must not* contain newlines
subject = ''.join(subject.splitlines())
message_for_activation = render_to_string('emails/activation_email.txt', context)
from_address = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
from_address = configuration_helpers.get_value('ACTIVATION_EMAIL_FROM_ADDRESS', from_address)
if settings.FEATURES.get('REROUTE_ACTIVATION_EMAIL'):
dest_addr = settings.FEATURES['REROUTE_ACTIVATION_EMAIL']
message_for_activation = ("Activation for %s (%s): %s\n" % (user, user.email, profile.name) +
'-' * 80 + '\n\n' + message_for_activation)
send_activation_email.delay(subject, message_for_activation, from_address, dest_addr)
route_enabled = settings.FEATURES.get('REROUTE_ACTIVATION_EMAIL')

root_url = configuration_helpers.get_value('LMS_ROOT_URL', settings.LMS_ROOT_URL)
msg = compose_activation_email(root_url, user, user_registration, route_enabled, profile.name)

from_address = configuration_helpers.get_value('ACTIVATION_EMAIL_FROM_ADDRESS') or (
configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
)
site = theming_helpers.get_current_site()
send_activation_email.delay(str(msg), site_id=site.pk, from_address=from_address)


@login_required
Expand Down
71 changes: 71 additions & 0 deletions common/templates/student/edx_ace/accountactivation/email/body.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{% extends 'ace_common/edx_ace/common/base_body.html' %}

{% load django_markup %}
{% load i18n %}
{% load static %}
{% block content %}
<table width="100%" align="left" border="0" cellpadding="0" cellspacing="0" role="presentation">
{% if route_enabled %}
<tr>
<td>
<p style="color: rgba(0,0,0,.75);">
{% filter force_escape %}
{% blocktrans %}This is a routed Account Activation email for {{ routed_user }} ({{ routed_user_email }}): {{ routed_profile_name }}{% endblocktrans %}
{% endfilter %}
<br />
</p>
</td>
</tr>
{% endif %}
<tr>
<td>
<h1>
{% trans "Account Activation" as header_msg %}{{ header_msg | force_escape }}
</h1>
<p style="color: rgba(0,0,0,.75);">
{% filter force_escape %}
{% blocktrans %}You're almost there! Use the link below to activate your account to access engaging, high-quality {{ platform_name }} courses. Note that you will not be able to log back into your account until you have activated it.{% endblocktrans %}
{% endfilter %}
<br />
</p>

{% filter force_escape %}
{% blocktrans asvar course_cta_text %}Activate Your Account{% endblocktrans %}
{% endfilter %}
{% include "ace_common/edx_ace/common/return_to_course_cta.html" with course_cta_text=course_cta_text course_cta_url=confirm_activation_link %}
</td>
</tr>
<tr>
<td>
<p style="color: rgba(0,0,0,.75);">
{% filter force_escape %}
{% blocktrans %}Enjoy learning with {{ platform_name }}.{% endblocktrans %}
{% endfilter %}
<br />
</p>
</td>
</tr>
<td>
<p style="color: rgba(0,0,0,.75);">
{% blocktrans trimmed asvar assist_msg %}
If you need help, please use our web form at {start_anchor_web}{{ support_url }}{end_anchor}.
{% endblocktrans %}
{% interpolate_html assist_msg start_anchor_web='<a href="'|add:support_url|add:'">'|safe end_anchor='</a>'|safe %}
<br />
</p>
</td>
<tr>

</tr>
<tr>
<td>
<p style="color: rgba(0,0,0,.75);">
{% filter force_escape %}
{% blocktrans %}This email message was automatically sent by {{ lms_url }} because someone attempted to create an account on {{ platform_name }} using this email address.{% endblocktrans %}
{% endfilter %}
<br />
</p>
</td>
</tr>
</table>
{% endblock %}
11 changes: 11 additions & 0 deletions common/templates/student/edx_ace/accountactivation/email/body.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{% load i18n %}{% autoescape off %}
{% blocktrans %}You're almost there! Use the link below to activate your account to access engaging, high-quality {{ platform_name }} courses. Note that you will not be able to log back into your account until you have activated it.{% endblocktrans %}

{{ confirm_activation_link }}

{% blocktrans %}Enjoy learning with {{ platform_name }}.{% endblocktrans %}

{% blocktrans %}If you need help, please use our web form at {{ support_url }}.{% endblocktrans %}

{% blocktrans %}This email message was automatically sent by {{ lms_url }} because someone attempted to create an account on {{ platform_name }} using this email address.{% endblocktrans %}
{% endautoescape %}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ platform_name }}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{% extends 'ace_common/edx_ace/common/base_head.html' %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{% load i18n %}
{% autoescape off %}
{% blocktrans trimmed %}Action Required: Activate your {{ platform_name }} account{% endblocktrans %}
{% endautoescape %}
23 changes: 0 additions & 23 deletions lms/templates/emails/activation_email.txt

This file was deleted.

2 changes: 0 additions & 2 deletions lms/templates/emails/activation_email_subject.txt

This file was deleted.