Merge pull request #18177 from edx/revert-16545-omar/fmo/multipart-email
Revert "Use edx-ace for the password reset email"
This commit is contained in:
@@ -27,8 +27,7 @@ for pkg_name in ['track.contexts', 'track.middleware', 'dd.dogapi']:
|
||||
|
||||
################################ EMAIL ########################################
|
||||
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
|
||||
EMAIL_FILE_PATH = '/edx/src/ace_messages/'
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
|
||||
################################# LMS INTEGRATION #############################
|
||||
|
||||
|
||||
@@ -10,22 +10,15 @@ from django.contrib.auth.forms import PasswordResetForm
|
||||
from django.contrib.auth.hashers import UNUSABLE_PASSWORD_PREFIX
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.validators import RegexValidator, slug_re
|
||||
from django.forms import widgets
|
||||
from django.template import loader
|
||||
from django.utils.http import int_to_base36
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.validators import RegexValidator, slug_re
|
||||
|
||||
from edx_ace import ace
|
||||
from edx_ace.recipient import Recipient
|
||||
from openedx.core.djangoapps.ace_common.template_context import get_base_template_context
|
||||
from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from openedx.core.djangoapps.user_api import accounts as accounts_settings
|
||||
from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
|
||||
from student.message_types import PasswordReset
|
||||
from student.models import CourseEnrollmentAllowed
|
||||
from util.password_policy_validators import password_max_length, password_min_length, validate_password
|
||||
|
||||
@@ -54,39 +47,41 @@ class PasswordResetFormNoActive(PasswordResetForm):
|
||||
raise forms.ValidationError(self.error_messages['unusable'])
|
||||
return email
|
||||
|
||||
def save(self, # pylint: disable=arguments-differ
|
||||
use_https=False,
|
||||
token_generator=default_token_generator,
|
||||
request=None,
|
||||
**_kwargs):
|
||||
def save(
|
||||
self,
|
||||
subject_template_name='emails/password_reset_subject.txt',
|
||||
email_template_name='registration/password_reset_email.html',
|
||||
use_https=False,
|
||||
token_generator=default_token_generator,
|
||||
from_email=configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL),
|
||||
request=None
|
||||
):
|
||||
"""
|
||||
Generates a one-use only link for resetting password and sends to the
|
||||
user.
|
||||
"""
|
||||
# This import is here because we are copying and modifying the .save from Django 1.4.5's
|
||||
# django.contrib.auth.forms.PasswordResetForm directly, which has this import in this place.
|
||||
from django.core.mail import send_mail
|
||||
for user in self.users_cache:
|
||||
site = Site.objects.get_current()
|
||||
message_context = get_base_template_context(site)
|
||||
|
||||
message_context.update({
|
||||
'request': request, # Used by google_analytics_tracking_pixel
|
||||
# TODO: This overrides `platform_name` from `get_base_template_context` to make the tests passes
|
||||
'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
|
||||
'reset_link': '{protocol}://{site}{link}'.format(
|
||||
protocol='https' if use_https else 'http',
|
||||
site=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME),
|
||||
link=reverse('password_reset_confirm', kwargs={
|
||||
'uidb36': int_to_base36(user.id),
|
||||
'token': token_generator.make_token(user),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
msg = PasswordReset().personalize(
|
||||
recipient=Recipient(user.username, user.email),
|
||||
language=get_user_preference(user, LANGUAGE_KEY),
|
||||
user_context=message_context,
|
||||
site_name = configuration_helpers.get_value(
|
||||
'SITE_NAME',
|
||||
settings.SITE_NAME
|
||||
)
|
||||
ace.send(msg)
|
||||
context = {
|
||||
'email': user.email,
|
||||
'site_name': site_name,
|
||||
'uid': int_to_base36(user.id),
|
||||
'user': user,
|
||||
'token': token_generator.make_token(user),
|
||||
'protocol': 'https' if use_https else 'http',
|
||||
'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME)
|
||||
}
|
||||
subject = loader.render_to_string(subject_template_name, context)
|
||||
# Email subject *must not* contain newlines
|
||||
subject = subject.replace('\n', '')
|
||||
email = loader.render_to_string(email_template_name, context)
|
||||
send_mail(subject, email, from_email, [user.email])
|
||||
|
||||
|
||||
class TrueCheckbox(widgets.CheckboxInput):
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
"""
|
||||
ACE message types for the student module.
|
||||
"""
|
||||
from django.conf import settings
|
||||
|
||||
from edx_ace.message import MessageType
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
|
||||
|
||||
class PasswordReset(MessageType):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PasswordReset, self).__init__(*args, **kwargs)
|
||||
|
||||
self.options['transactional'] = True
|
||||
self.options['from_address'] = configuration_helpers.get_value(
|
||||
'email_from_address', settings.DEFAULT_FROM_EMAIL
|
||||
)
|
||||
@@ -15,7 +15,6 @@ FAKE_SITE = {
|
||||
"university": "fakeuniversity",
|
||||
"course_org_filter": "fakeorg",
|
||||
"platform_name": "Fake University",
|
||||
"PLATFORM_NAME": "Fake University",
|
||||
"email_from_address": "no-reply@fakeuniversity.com",
|
||||
"REGISTRATION_EXTRA_FIELDS": {
|
||||
"address1": "required",
|
||||
|
||||
@@ -11,7 +11,6 @@ from django.contrib.auth.hashers import UNUSABLE_PASSWORD_PREFIX
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.core.cache import cache
|
||||
from django.core import mail
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test.client import RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
@@ -111,12 +110,13 @@ class ResetPasswordTests(EventTestMixin, CacheIsolationTestCase):
|
||||
cache.clear()
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', "Test only valid in LMS")
|
||||
@ddt.data('plain_text', 'html')
|
||||
def test_reset_password_email(self, body_type):
|
||||
@patch('django.core.mail.send_mail')
|
||||
@patch('student.views.management.render_to_string', Mock(side_effect=mock_render_to_string, autospec=True))
|
||||
def test_reset_password_email(self, send_email):
|
||||
"""Tests contents of reset password email, and that user is not active"""
|
||||
|
||||
good_req = self.request_factory.post('/password_reset/', {'email': self.user.email})
|
||||
good_req.user = self.user
|
||||
good_req.site = Mock(domain='example.com')
|
||||
dop_client = ClientFactory()
|
||||
dop_access_token = AccessTokenFactory(user=self.user, client=dop_client)
|
||||
RefreshTokenFactory(user=self.user, client=dop_client, access_token=dop_access_token)
|
||||
@@ -130,35 +130,26 @@ class ResetPasswordTests(EventTestMixin, CacheIsolationTestCase):
|
||||
self.assertFalse(dot_models.AccessToken.objects.filter(user=self.user).exists())
|
||||
self.assertFalse(dot_models.RefreshToken.objects.filter(user=self.user).exists())
|
||||
obj = json.loads(good_resp.content)
|
||||
self.assertTrue(obj['success'])
|
||||
self.assertIn('e-mailed you instructions for setting your password', obj['value'])
|
||||
self.assertEquals(obj, {
|
||||
'success': True,
|
||||
'value': "('registration/password_reset_done.html', [])",
|
||||
})
|
||||
|
||||
from_email = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
|
||||
sent_message = mail.outbox[0]
|
||||
|
||||
bodies = {
|
||||
'plain_text': sent_message.body,
|
||||
'html': sent_message.alternatives[0][0],
|
||||
}
|
||||
|
||||
body = bodies[body_type]
|
||||
|
||||
self.assertIn("Password reset", sent_message.subject)
|
||||
self.assertIn("You're receiving this e-mail because you requested a password reset", body)
|
||||
self.assertEquals(sent_message.from_email, from_email)
|
||||
self.assertEquals(len(sent_message.to), 1)
|
||||
self.assertIn(self.user.email, sent_message.to)
|
||||
(subject, msg, from_addr, to_addrs) = send_email.call_args[0]
|
||||
self.assertIn("Password reset", subject)
|
||||
self.assertIn("You're receiving this e-mail because you requested a password reset", msg)
|
||||
self.assertEquals(from_addr, configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL))
|
||||
self.assertEquals(len(to_addrs), 1)
|
||||
self.assertIn(self.user.email, to_addrs)
|
||||
|
||||
self.assert_event_emitted(
|
||||
SETTING_CHANGE_INITIATED, user_id=self.user.id, setting=u'password', old=None, new=None,
|
||||
)
|
||||
|
||||
# Test that the user is not active
|
||||
#test that the user is not active
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
self.assertFalse(self.user.is_active)
|
||||
|
||||
self.assertIn('password_reset_confirm/', body)
|
||||
re.search(r'password_reset_confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/', body).groupdict()
|
||||
re.search(r'password_reset_confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/', msg).groupdict()
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', "Test only valid in LMS")
|
||||
@patch('django.core.mail.send_mail')
|
||||
@@ -171,7 +162,6 @@ class ResetPasswordTests(EventTestMixin, CacheIsolationTestCase):
|
||||
req = self.request_factory.post(
|
||||
'/password_reset/', {'email': self.user.email}
|
||||
)
|
||||
req.site = Mock(domain='example.com')
|
||||
req.is_secure = Mock(return_value=is_secure)
|
||||
req.user = self.user
|
||||
password_reset(req)
|
||||
@@ -199,7 +189,6 @@ class ResetPasswordTests(EventTestMixin, CacheIsolationTestCase):
|
||||
'/password_reset/', {'email': self.user.email}
|
||||
)
|
||||
req.user = self.user
|
||||
req.site = Mock(domain='example.com')
|
||||
password_reset(req)
|
||||
_, msg, _, _ = send_email.call_args[0]
|
||||
|
||||
@@ -217,8 +206,8 @@ class ResetPasswordTests(EventTestMixin, CacheIsolationTestCase):
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', "Test only valid in LMS")
|
||||
@patch("openedx.core.djangoapps.site_configuration.helpers.get_value", fake_get_value)
|
||||
@ddt.data('plain_text', 'html')
|
||||
def test_reset_password_email_configuration_override(self, body_type):
|
||||
@patch('django.core.mail.send_mail')
|
||||
def test_reset_password_email_configuration_override(self, send_email):
|
||||
"""
|
||||
Tests that the right url domain and platform name is included in
|
||||
the reset password email
|
||||
@@ -227,28 +216,18 @@ class ResetPasswordTests(EventTestMixin, CacheIsolationTestCase):
|
||||
'/password_reset/', {'email': self.user.email}
|
||||
)
|
||||
req.get_host = Mock(return_value=None)
|
||||
req.site = Mock(domain='example.com')
|
||||
req.user = self.user
|
||||
password_reset(req)
|
||||
_, msg, from_addr, _ = send_email.call_args[0]
|
||||
|
||||
with patch('crum.get_current_request', return_value=req):
|
||||
password_reset(req)
|
||||
reset_msg = "you requested a password reset for your user account at {}".format(fake_get_value('platform_name'))
|
||||
|
||||
sent_message = mail.outbox[0]
|
||||
bodies = {
|
||||
'plain_text': sent_message.body,
|
||||
'html': sent_message.alternatives[0][0],
|
||||
}
|
||||
|
||||
body = bodies[body_type]
|
||||
|
||||
reset_msg = "you requested a password reset for your user account at {}".format(fake_get_value('PLATFORM_NAME'))
|
||||
|
||||
self.assertIn(reset_msg, body)
|
||||
self.assertIn(reset_msg, msg)
|
||||
|
||||
self.assert_event_emitted(
|
||||
SETTING_CHANGE_INITIATED, user_id=self.user.id, setting=u'password', old=None, new=None
|
||||
)
|
||||
self.assertEqual(sent_message.from_email, "no-reply@fakeuniversity.com")
|
||||
self.assertEqual(from_addr, "no-reply@fakeuniversity.com")
|
||||
|
||||
@ddt.data(
|
||||
('invalidUid', 'invalid_token'),
|
||||
@@ -372,7 +351,6 @@ class ResetPasswordTests(EventTestMixin, CacheIsolationTestCase):
|
||||
'/password_reset/', {'email': self.user.email}
|
||||
)
|
||||
req.user = self.user
|
||||
req.site = Mock(domain='example.com')
|
||||
password_reset(req)
|
||||
subj, _, _, _ = send_email.call_args[0]
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
{% extends 'ace_common/edx_ace/common/base_body.html' %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
<table width="100%" align="left" border="0" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td>
|
||||
<h1>
|
||||
{% trans "Password Reset" %}
|
||||
</h1>
|
||||
<p style="color: rgba(0,0,0,.75);">
|
||||
{% blocktrans %}You're receiving this e-mail because you requested a password reset for your user account at {{ platform_name }}.{% endblocktrans %}
|
||||
<br />
|
||||
</p>
|
||||
|
||||
{% if failed %}
|
||||
<p style="color: rgba(0,0,0,.75);">
|
||||
{% blocktrans %}However, there is currently no user account associated with your email address: {{ email_address }}.{% endblocktrans %}
|
||||
<br />
|
||||
</p>
|
||||
|
||||
<p style="color: rgba(0,0,0,.75);">
|
||||
{% trans "If you did not request this change, you can ignore this email." %}
|
||||
<br />
|
||||
</p>
|
||||
{% else %}
|
||||
<p style="color: rgba(0,0,0,.75);">
|
||||
{% trans "If you didn't request this change, you can disregard this email - we have not yet reset your password." %}
|
||||
<br />
|
||||
</p>
|
||||
|
||||
{% trans "Change my Password" as course_cta_text %}
|
||||
|
||||
{% include "ace_common/edx_ace/common/return_to_course_cta.html" with course_cta_text=course_cta_text course_cta_url=reset_link %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endblock %}
|
||||
@@ -1 +0,0 @@
|
||||
{{ platform_name }}
|
||||
@@ -1 +0,0 @@
|
||||
{% extends 'ace_common/edx_ace/common/base_head.html' %}
|
||||
@@ -1,4 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% autoescape off %}
|
||||
{% blocktrans trimmed %}Password reset on {{ platform_name }}{% endblocktrans %}
|
||||
{% endautoescape %}
|
||||
@@ -164,18 +164,13 @@ class StudentAccountUpdateTest(CacheIsolationTestCase, UrlResetMixin):
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
|
||||
# Verify that the body contains the failed password reset message
|
||||
sent_message = mail.outbox[0]
|
||||
text_body = sent_message.body
|
||||
html_body = sent_message.alternatives[0][0]
|
||||
|
||||
for email_body in [text_body, html_body]:
|
||||
msg = 'However, there is currently no user account associated with your email address: {email}'.format(
|
||||
email_body = mail.outbox[0].body
|
||||
self.assertIn(
|
||||
'However, there is currently no user account associated with your email address: {email}'.format(
|
||||
email=bad_email
|
||||
)
|
||||
|
||||
assert u'reset for your user account at {}'.format(settings.PLATFORM_NAME) in email_body
|
||||
assert 'password_reset_confirm' not in email_body, 'The link should not be added if user was not found'
|
||||
assert msg in email_body
|
||||
),
|
||||
email_body,
|
||||
)
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_password_change_logged_out(self, send_email):
|
||||
|
||||
@@ -9,22 +9,19 @@ from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.mail import send_mail
|
||||
from django.core.urlresolvers import resolve, reverse
|
||||
from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
|
||||
from django.shortcuts import redirect
|
||||
from django.template import loader
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django_countries import countries
|
||||
import third_party_auth
|
||||
|
||||
from edx_ace import ace
|
||||
from edx_ace.recipient import Recipient
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from lms.djangoapps.commerce.models import CommerceConfiguration
|
||||
from lms.djangoapps.commerce.utils import EcommerceService
|
||||
from openedx.core.djangoapps.ace_common.template_context import get_base_template_context
|
||||
from openedx.core.djangoapps.commerce.utils import ecommerce_api_client
|
||||
from openedx.core.djangoapps.external_auth.login_and_register import login as external_auth_login
|
||||
from openedx.core.djangoapps.external_auth.login_and_register import register as external_auth_register
|
||||
@@ -51,7 +48,6 @@ from openedx.features.enterprise_support.utils import (
|
||||
update_account_settings_context_for_enterprise,
|
||||
)
|
||||
from student.helpers import destroy_oauth_tokens, get_next_url_for_login_page
|
||||
from student.message_types import PasswordReset
|
||||
from student.models import UserProfile
|
||||
from student.views import register_user as old_register_view, signin_user as old_login_view
|
||||
from third_party_auth import pipeline
|
||||
@@ -59,7 +55,6 @@ from third_party_auth.decorators import xframe_allow_whitelisted
|
||||
from util.bad_request_rate_limiter import BadRequestRateLimiter
|
||||
from util.date_utils import strftime_localized
|
||||
|
||||
|
||||
AUDIT_LOG = logging.getLogger("audit")
|
||||
log = logging.getLogger(__name__)
|
||||
User = get_user_model() # pylint:disable=invalid-name
|
||||
@@ -227,23 +222,20 @@ def password_change_request_handler(request):
|
||||
# no user associated with the email
|
||||
if configuration_helpers.get_value('ENABLE_PASSWORD_RESET_FAILURE_EMAIL',
|
||||
settings.FEATURES['ENABLE_PASSWORD_RESET_FAILURE_EMAIL']):
|
||||
|
||||
site = Site.objects.get_current()
|
||||
message_context = get_base_template_context(site)
|
||||
|
||||
message_context.update({
|
||||
context = {
|
||||
'failed': True,
|
||||
'request': request, # Used by google_analytics_tracking_pixel
|
||||
'email_address': email,
|
||||
})
|
||||
'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
|
||||
|
||||
msg = PasswordReset().personalize(
|
||||
recipient=Recipient(username='', email_address=email),
|
||||
language=settings.LANGUAGE_CODE,
|
||||
user_context=message_context,
|
||||
)
|
||||
|
||||
ace.send(msg)
|
||||
}
|
||||
subject = loader.render_to_string('emails/password_reset_subject.txt', context)
|
||||
subject = ''.join(subject.splitlines())
|
||||
message = loader.render_to_string('registration/password_reset_email.html', context)
|
||||
from_email = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
|
||||
try:
|
||||
send_mail(subject, message, from_email, [email])
|
||||
except Exception: # pylint: disable=broad-except
|
||||
log.exception(u'Unable to send password reset failure email notification from "%s"', from_email)
|
||||
except UserAPIInternalError as err:
|
||||
log.exception('Error occured during password change for user {email}: {error}'
|
||||
.format(email=email, error=err))
|
||||
|
||||
@@ -39,8 +39,7 @@ for log_name, log_level in LOG_OVERRIDES:
|
||||
|
||||
################################ EMAIL ########################################
|
||||
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
|
||||
EMAIL_FILE_PATH = '/edx/src/ace_messages/'
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
|
||||
############################ PYFS XBLOCKS SERVICE #############################
|
||||
# Set configuration for Django pyfilesystem
|
||||
|
||||
4
lms/templates/emails/password_reset_subject.txt
Normal file
4
lms/templates/emails/password_reset_subject.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
{% load i18n %}
|
||||
{% autoescape off %}
|
||||
{% blocktrans %}Password reset on {{ platform_name }}{% endblocktrans %}
|
||||
{% endautoescape %}
|
||||
@@ -7,12 +7,15 @@
|
||||
{% trans "If you did not request this change, you can ignore this email." %}
|
||||
{% else %}
|
||||
{% trans "Please go to the following page and choose a new password:" %}
|
||||
|
||||
{{ reset_link }}
|
||||
{% block reset_link %}
|
||||
{{ protocol }}://{{ site_name }}{% url 'password_reset_confirm' uidb36=uid token=token %}
|
||||
{% endblock %}
|
||||
|
||||
{% trans "If you didn't request this change, you can disregard this email - we have not yet reset your password." %}
|
||||
|
||||
{% trans "Thanks for using our site!" %}
|
||||
{% endif %}
|
||||
|
||||
{% blocktrans %}The {{ platform_name }} Team{% endblocktrans %}
|
||||
|
||||
{% endautoescape %}
|
||||
@@ -19,7 +19,7 @@ class AceCommonConfig(AppConfig):
|
||||
ProjectType.LMS: {
|
||||
SettingsType.AWS: {PluginSettings.RELATIVE_PATH: u'settings.aws'},
|
||||
SettingsType.COMMON: {PluginSettings.RELATIVE_PATH: u'settings.common'},
|
||||
SettingsType.DEVSTACK: {PluginSettings.RELATIVE_PATH: u'settings.devstack'},
|
||||
SettingsType.DEVSTACK: {PluginSettings.RELATIVE_PATH: u'settings.common'},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,3 @@ def plugin_settings(settings):
|
||||
'ACE_CHANNEL_SAILTHRU_API_SECRET', settings.ACE_CHANNEL_SAILTHRU_API_SECRET,
|
||||
)
|
||||
settings.ACE_ROUTING_KEY = settings.ENV_TOKENS.get('ACE_ROUTING_KEY', settings.ACE_ROUTING_KEY)
|
||||
|
||||
settings.ACE_CHANNEL_DEFAULT_EMAIL = settings.ENV_TOKENS.get(
|
||||
'ACE_CHANNEL_DEFAULT_EMAIL', settings.ACE_CHANNEL_DEFAULT_EMAIL
|
||||
)
|
||||
settings.ACE_CHANNEL_TRANSACTIONAL_EMAIL = settings.ENV_TOKENS.get(
|
||||
'ACE_CHANNEL_TRANSACTIONAL_EMAIL', settings.ACE_CHANNEL_TRANSACTIONAL_EMAIL
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
def plugin_settings(settings):
|
||||
settings.ACE_ENABLED_CHANNELS = [
|
||||
'django_email'
|
||||
'file_email'
|
||||
]
|
||||
settings.ACE_ENABLED_POLICIES = [
|
||||
'bulk_email_optout'
|
||||
@@ -9,8 +9,6 @@ def plugin_settings(settings):
|
||||
settings.ACE_CHANNEL_SAILTHRU_TEMPLATE_NAME = 'Automated Communication Engine Email'
|
||||
settings.ACE_CHANNEL_SAILTHRU_API_KEY = None
|
||||
settings.ACE_CHANNEL_SAILTHRU_API_SECRET = None
|
||||
settings.ACE_CHANNEL_DEFAULT_EMAIL = 'django_email'
|
||||
settings.ACE_CHANNEL_TRANSACTIONAL_EMAIL = 'django_email'
|
||||
|
||||
settings.ACE_ROUTING_KEY = 'edx.core.low'
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
"""
|
||||
Settings for edX ACE on devstack.
|
||||
"""
|
||||
|
||||
from openedx.core.djangoapps.ace_common.settings import common
|
||||
|
||||
|
||||
def plugin_settings(settings):
|
||||
"""
|
||||
Override common settings and use `file_email` for better debugging.
|
||||
"""
|
||||
common.plugin_settings(settings)
|
||||
|
||||
settings.ACE_ENABLED_CHANNELS = [
|
||||
'file_email'
|
||||
]
|
||||
|
||||
settings.ACE_CHANNEL_DEFAULT_EMAIL = 'file_email'
|
||||
settings.ACE_CHANNEL_TRANSACTIONAL_EMAIL = 'file_email'
|
||||
@@ -20,9 +20,9 @@
|
||||
{% block preview_text %}{% endblock %}
|
||||
</div>
|
||||
|
||||
{% for image_src in channel.tracker_image_sources %}
|
||||
<img src="{image_src}" alt="" role="presentation" aria-hidden="true" />
|
||||
{% endfor %}
|
||||
{# Note {beacon_src} is not a template variable that is evaluated by the Django template engine. It is evaluated by #}
|
||||
{# Sailthru when the email is sent. Other email providers would need to replace this variable in the HTML as well. #}
|
||||
<img src="{beacon_src}" alt="" role="presentation" aria-hidden="true" />
|
||||
|
||||
{% google_analytics_tracking_pixel %}
|
||||
|
||||
@@ -157,13 +157,17 @@
|
||||
<tr>
|
||||
<!-- Actions -->
|
||||
<td style="padding-bottom: 20px;">
|
||||
{% for action_link_url, action_link_text in channel.action_links %}
|
||||
<p>
|
||||
<a href="{{ action_link_url }}" style="color: #005686">
|
||||
<font color="#005686"><b>{{ action_link_text }}</b></font>
|
||||
</a>
|
||||
</p>
|
||||
{% endfor %}
|
||||
{# Note that these variables are evaluated by Sailthru, not the Django template engine #}
|
||||
<p>
|
||||
<a href="{view_url}" style="color: #005686">
|
||||
<font color="#005686"><b>{% trans "View on Web" %}</b></font>
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="{optout_confirm_url}" style="color: #005686">
|
||||
<font color="#005686"><b>{% trans "Unsubscribe from this list" %}</b></font>
|
||||
</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -7,8 +7,8 @@ import attr
|
||||
import ddt
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from edx_ace.channel import ChannelMap, ChannelType
|
||||
from edx_ace.test_utils import StubPolicy, patch_policies
|
||||
from edx_ace.channel import ChannelType
|
||||
from edx_ace.test_utils import StubPolicy, patch_channels, patch_policies
|
||||
from edx_ace.utils.date import serialize
|
||||
from freezegun import freeze_time
|
||||
from mock import Mock, patch
|
||||
@@ -383,16 +383,11 @@ class ScheduleSendEmailTestMixin(FilteredQueryCountMixin):
|
||||
)
|
||||
|
||||
patch_policies(self, [StubPolicy([ChannelType.PUSH])])
|
||||
|
||||
mock_channel = Mock(
|
||||
channel_type=ChannelType.EMAIL,
|
||||
action_links=[],
|
||||
tracker_image_sources=[],
|
||||
name='test_channel',
|
||||
channel_type=ChannelType.EMAIL
|
||||
)
|
||||
|
||||
channel_map = ChannelMap([
|
||||
['sailthru', mock_channel],
|
||||
])
|
||||
patch_channels(self, [mock_channel])
|
||||
|
||||
sent_messages = []
|
||||
with self.settings(TEMPLATES=self._get_template_overrides()):
|
||||
@@ -416,9 +411,8 @@ class ScheduleSendEmailTestMixin(FilteredQueryCountMixin):
|
||||
|
||||
with self.assertNumQueries(NUM_QUERIES_PER_MESSAGE_DELIVERY):
|
||||
with patch('analytics.track') as mock_analytics_track:
|
||||
with patch('edx_ace.channel.channels', return_value=channel_map):
|
||||
self.deliver_task(*sent_messages[0])
|
||||
self.assertEqual(mock_analytics_track.call_count, 1)
|
||||
self.deliver_task(*sent_messages[0])
|
||||
self.assertEqual(mock_analytics_track.call_count, 1)
|
||||
|
||||
self.assertEqual(mock_channel.deliver.call_count, 1)
|
||||
for (_name, (_msg, email), _kwargs) in mock_channel.deliver.mock_calls:
|
||||
|
||||
@@ -11,7 +11,6 @@ from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.conf import settings
|
||||
from django.core.validators import validate_email, ValidationError
|
||||
from django.http import HttpResponseForbidden
|
||||
from openedx.core.djangoapps.theming.helpers import get_current_request
|
||||
from six import text_type
|
||||
|
||||
from student.models import User, UserProfile, Registration
|
||||
@@ -433,8 +432,7 @@ def request_password_change(email, is_secure):
|
||||
# and email it to the user.
|
||||
form.save(
|
||||
from_email=configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL),
|
||||
use_https=is_secure,
|
||||
request=get_current_request(),
|
||||
use_https=is_secure
|
||||
)
|
||||
else:
|
||||
# No user with the provided email address exists.
|
||||
|
||||
@@ -19,7 +19,6 @@ from nose.plugins.attrib import attr
|
||||
from nose.tools import raises
|
||||
from six import iteritems
|
||||
|
||||
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
|
||||
from openedx.core.djangoapps.user_api.accounts import PRIVATE_VISIBILITY, USERNAME_MAX_LENGTH
|
||||
from openedx.core.djangoapps.user_api.accounts.api import (
|
||||
activate_account,
|
||||
@@ -432,13 +431,8 @@ class AccountCreationActivationAndPasswordChangeTest(TestCase):
|
||||
activation_key = create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
|
||||
activate_account(activation_key)
|
||||
|
||||
request = RequestFactory().post('/password')
|
||||
request.user = Mock()
|
||||
request.site = SiteFactory()
|
||||
|
||||
with patch('crum.get_current_request', return_value=request):
|
||||
# Request a password change
|
||||
request_password_change(self.EMAIL, self.IS_SECURE)
|
||||
# Request a password change
|
||||
request_password_change(self.EMAIL, self.IS_SECURE)
|
||||
|
||||
# Verify that one email message has been sent
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
@@ -462,12 +456,7 @@ class AccountCreationActivationAndPasswordChangeTest(TestCase):
|
||||
# Create an account, but do not activate it
|
||||
create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
|
||||
|
||||
request = RequestFactory().post('/password')
|
||||
request.user = Mock()
|
||||
request.site = SiteFactory()
|
||||
|
||||
with patch('crum.get_current_request', return_value=request):
|
||||
request_password_change(self.EMAIL, self.IS_SECURE)
|
||||
request_password_change(self.EMAIL, self.IS_SECURE)
|
||||
|
||||
# Verify that the activation email was still sent
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
|
||||
@@ -64,7 +64,7 @@ django-waffle==0.12.0
|
||||
django-webpack-loader==0.4.1
|
||||
djangorestframework-jwt
|
||||
dogapi==1.2.1 # Python bindings to Datadog's API, for metrics gathering
|
||||
edx-ace==0.1.7
|
||||
edx-ace==0.1.6
|
||||
edx-analytics-data-api-client
|
||||
edx-ccx-keys
|
||||
edx-celeryutils
|
||||
|
||||
@@ -109,7 +109,7 @@ dm.xmlsec.binding==1.3.3 # via python-saml
|
||||
docopt==0.6.2
|
||||
docutils==0.14 # via botocore
|
||||
dogapi==1.2.1
|
||||
edx-ace==0.1.7
|
||||
edx-ace==0.1.6
|
||||
edx-analytics-data-api-client==0.14.4
|
||||
edx-ccx-keys==0.2.1
|
||||
edx-celeryutils==0.2.7
|
||||
|
||||
@@ -128,7 +128,7 @@ dm.xmlsec.binding==1.3.3
|
||||
docopt==0.6.2
|
||||
docutils==0.14
|
||||
dogapi==1.2.1
|
||||
edx-ace==0.1.7
|
||||
edx-ace==0.1.6
|
||||
edx-analytics-data-api-client==0.14.4
|
||||
edx-ccx-keys==0.2.1
|
||||
edx-celeryutils==0.2.7
|
||||
|
||||
@@ -123,7 +123,7 @@ dm.xmlsec.binding==1.3.3
|
||||
docopt==0.6.2
|
||||
docutils==0.14
|
||||
dogapi==1.2.1
|
||||
edx-ace==0.1.7
|
||||
edx-ace==0.1.6
|
||||
edx-analytics-data-api-client==0.14.4
|
||||
edx-ccx-keys==0.2.1
|
||||
edx-celeryutils==0.2.7
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
|
||||
<!-- TEST RED THEME MARKER: Do not remove this comment, it is used by the tests to tell if this theme was used -->
|
||||
|
||||
{% for image_src in channel.tracker_image_sources %}
|
||||
<img src="{image_src}" alt="" role="presentation" aria-hidden="true" />
|
||||
{% endfor %}
|
||||
{# Note {beacon_src} is not a template variable that is evaluated by the Django template engine. It is evaluated by #}
|
||||
{# Sailthru when the email is sent. Other email providers would need to replace this variable in the HTML as well. #}
|
||||
<img src="{beacon_src}" alt="" role="presentation" aria-hidden="true" />
|
||||
|
||||
{% google_analytics_tracking_pixel %}
|
||||
|
||||
@@ -159,13 +159,17 @@
|
||||
<tr>
|
||||
<!-- Actions -->
|
||||
<td style="padding-bottom: 20px;">
|
||||
{% for action_link_url, action_link_text in channel.action_links %}
|
||||
<p>
|
||||
<a href="{{ action_link_url }}" style="color: #960909">
|
||||
<font color="#960909"><b>{{ action_link_text }}</b></font>
|
||||
</a>
|
||||
</p>
|
||||
{% endfor %}
|
||||
{# Note that these variables are evaluated by Sailthru, not the Django template engine #}
|
||||
<p>
|
||||
<a href="{view_url}" style="color: #960909">
|
||||
<font color="#960909"><b>{% trans "View on Web" %}</b></font>
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="{optout_confirm_url}" style="color: #960909">
|
||||
<font color="#960909"><b>{% trans "Unsubscribe from this list" %}</b></font>
|
||||
</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
Reference in New Issue
Block a user