diff --git a/common/djangoapps/student/message_types.py b/common/djangoapps/student/message_types.py index da89a2a3f56b..0942e6f4690e 100644 --- a/common/djangoapps/student/message_types.py +++ b/common/djangoapps/student/message_types.py @@ -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 diff --git a/common/djangoapps/student/tasks.py b/common/djangoapps/student/tasks.py index 23b9481c3052..609cc325393e 100644 --- a/common/djangoapps/student/tasks.py +++ b/common/djangoapps/student/tasks.py @@ -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 @@ -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 diff --git a/common/djangoapps/student/views/management.py b/common/djangoapps/student/views/management.py index 290a6a293779..bb382d775cec 100644 --- a/common/djangoapps/student/views/management.py +++ b/common/djangoapps/student/views/management.py @@ -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 @@ -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, @@ -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 diff --git a/common/templates/student/edx_ace/accountactivation/email/body.html b/common/templates/student/edx_ace/accountactivation/email/body.html new file mode 100644 index 000000000000..85e31196196b --- /dev/null +++ b/common/templates/student/edx_ace/accountactivation/email/body.html @@ -0,0 +1,71 @@ +{% extends 'ace_common/edx_ace/common/base_body.html' %} + +{% load django_markup %} +{% load i18n %} +{% load static %} +{% block content %} + +{% if route_enabled %} + + + +{% endif %} + + + + + + + + + + + + + +
+

+ {% filter force_escape %} + {% blocktrans %}This is a routed Account Activation email for {{ routed_user }} ({{ routed_user_email }}): {{ routed_profile_name }}{% endblocktrans %} + {% endfilter %} +
+

+
+

+ {% trans "Account Activation" as header_msg %}{{ header_msg | force_escape }} +

+

+ {% 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 %} +
+

+ + {% 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 %} +
+

+ {% filter force_escape %} + {% blocktrans %}Enjoy learning with {{ platform_name }}.{% endblocktrans %} + {% endfilter %} +
+

+
+

+ {% 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=''|safe end_anchor=''|safe %} +
+

+
+

+ {% 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 %} +
+

+
+{% endblock %} diff --git a/common/templates/student/edx_ace/accountactivation/email/body.txt b/common/templates/student/edx_ace/accountactivation/email/body.txt new file mode 100644 index 000000000000..64d529365219 --- /dev/null +++ b/common/templates/student/edx_ace/accountactivation/email/body.txt @@ -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 %} diff --git a/common/templates/student/edx_ace/accountactivation/email/form_name.txt b/common/templates/student/edx_ace/accountactivation/email/form_name.txt new file mode 100644 index 000000000000..dcbc23c00480 --- /dev/null +++ b/common/templates/student/edx_ace/accountactivation/email/form_name.txt @@ -0,0 +1 @@ +{{ platform_name }} diff --git a/common/templates/student/edx_ace/accountactivation/email/head.html b/common/templates/student/edx_ace/accountactivation/email/head.html new file mode 100644 index 000000000000..366ada7ad92e --- /dev/null +++ b/common/templates/student/edx_ace/accountactivation/email/head.html @@ -0,0 +1 @@ +{% extends 'ace_common/edx_ace/common/base_head.html' %} diff --git a/common/templates/student/edx_ace/accountactivation/email/subject.txt b/common/templates/student/edx_ace/accountactivation/email/subject.txt new file mode 100644 index 000000000000..1fbb6356e860 --- /dev/null +++ b/common/templates/student/edx_ace/accountactivation/email/subject.txt @@ -0,0 +1,4 @@ +{% load i18n %} +{% autoescape off %} +{% blocktrans trimmed %}Action Required: Activate your {{ platform_name }} account{% endblocktrans %} +{% endautoescape %} diff --git a/lms/templates/emails/activation_email.txt b/lms/templates/emails/activation_email.txt deleted file mode 100644 index 4c4d431a0c8a..000000000000 --- a/lms/templates/emails/activation_email.txt +++ /dev/null @@ -1,23 +0,0 @@ -<%! from django.utils.translation import ugettext as _ %> -${_("You're almost there! Use the link 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.").format(platform_name=platform_name)} - -% if is_secure: -https://${ site }/activate/${ key } -% else: -http://${ site }/activate/${ key } -% endif - -${_("Enjoy learning with {platform_name}.").format(platform_name=platform_name)} - -${_("The {platform_name} Team").format(platform_name=platform_name)} - -${_("If you need help, please use our web form at {support_url} or email {support_email}.").format( - support_url=support_url, support_email=support_email -)} - -${_("This email message was automatically sent by {lms_url} because someone attempted to create an " -"account on {platform_name} using this email address.").format( - lms_url=lms_url, platform_name=platform_name -)} \ No newline at end of file diff --git a/lms/templates/emails/activation_email_subject.txt b/lms/templates/emails/activation_email_subject.txt deleted file mode 100644 index cdb52fe45884..000000000000 --- a/lms/templates/emails/activation_email_subject.txt +++ /dev/null @@ -1,2 +0,0 @@ -<%! from django.utils.translation import ugettext as _ %> -${_("Action Required: Activate your {platform_name} account").format(platform_name=platform_name)}