Send verification status email through sailthru
Support needs to inform a learner different states of its id-verification.In order to achieve it email will be sent to learner about its verification state using sailthru. LEARNER-617
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-04-25 12:00
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('email_marketing', '0009_remove_emailmarketingconfiguration_sailthru_activation_template'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='emailmarketingconfiguration',
|
||||
name='sailthru_verification_failed_template',
|
||||
field=models.CharField(blank=True, help_text='Sailthru send template to use on failed ID verification.', max_length=20),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='emailmarketingconfiguration',
|
||||
name='sailthru_verification_passed_template',
|
||||
field=models.CharField(blank=True, help_text='Sailthru send template to use on passed ID verification.', max_length=20),
|
||||
),
|
||||
]
|
||||
@@ -78,6 +78,22 @@ class EmailMarketingConfiguration(ConfigurationModel):
|
||||
)
|
||||
)
|
||||
|
||||
sailthru_verification_passed_template = models.fields.CharField(
|
||||
max_length=20,
|
||||
blank=True,
|
||||
help_text=_(
|
||||
"Sailthru send template to use on passed ID verification."
|
||||
)
|
||||
)
|
||||
|
||||
sailthru_verification_failed_template = models.fields.CharField(
|
||||
max_length=20,
|
||||
blank=True,
|
||||
help_text=_(
|
||||
"Sailthru send template to use on failed ID verification."
|
||||
)
|
||||
)
|
||||
|
||||
sailthru_upgrade_template = models.fields.CharField(
|
||||
max_length=20,
|
||||
blank=True,
|
||||
|
||||
@@ -36,7 +36,12 @@ from lms.djangoapps.commerce.models import CommerceConfiguration
|
||||
from lms.djangoapps.commerce.tests import TEST_API_URL, TEST_PAYMENT_DATA, TEST_PUBLIC_URL_ROOT
|
||||
from lms.djangoapps.commerce.utils import EcommerceService
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, VerificationDeadline
|
||||
from lms.djangoapps.verify_student.views import PayAndVerifyView, checkout_with_ecommerce_service, render_to_response
|
||||
from lms.djangoapps.verify_student.views import (
|
||||
PayAndVerifyView,
|
||||
checkout_with_ecommerce_service,
|
||||
render_to_response,
|
||||
EmailMarketingConfiguration
|
||||
)
|
||||
from openedx.core.djangoapps.embargo.test_utils import restrict_course
|
||||
from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme
|
||||
from openedx.core.djangoapps.user_api.accounts.api import get_account_settings
|
||||
@@ -840,7 +845,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
self.assertContains(response, "verification deadline")
|
||||
self.assertContains(response, verification_deadline_in_past)
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {'EMBARGO': True})
|
||||
@patch.dict(settings.FEATURES, {'EMBARGO': True})
|
||||
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
|
||||
def test_embargo_restrict(self, payment_flow):
|
||||
course = self._create_course("verified")
|
||||
@@ -850,7 +855,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
||||
response = self._get_page(payment_flow, course.id, expected_status_code=302)
|
||||
self.assertRedirects(response, redirect_url)
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {'EMBARGO': True})
|
||||
@patch.dict(settings.FEATURES, {'EMBARGO': True})
|
||||
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
|
||||
def test_embargo_allow(self, payment_flow):
|
||||
course = self._create_course("verified")
|
||||
@@ -1710,7 +1715,7 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase):
|
||||
self.assertIn('JSON should be dict', response.content)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
@mock.patch(
|
||||
@patch(
|
||||
'lms.djangoapps.verify_student.ssencrypt.has_valid_signature',
|
||||
mock.Mock(side_effect=mocked_has_valid_signature)
|
||||
)
|
||||
@@ -1735,7 +1740,7 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase):
|
||||
self.assertIn('Access key invalid', response.content)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
@mock.patch(
|
||||
@patch(
|
||||
'lms.djangoapps.verify_student.ssencrypt.has_valid_signature',
|
||||
mock.Mock(side_effect=mocked_has_valid_signature)
|
||||
)
|
||||
@@ -1760,14 +1765,17 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase):
|
||||
self.assertIn('edX ID Invalid-Id not found', response.content)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
@mock.patch(
|
||||
@patch(
|
||||
'lms.djangoapps.verify_student.ssencrypt.has_valid_signature',
|
||||
mock.Mock(side_effect=mocked_has_valid_signature)
|
||||
)
|
||||
def test_pass_result(self):
|
||||
@patch('lms.djangoapps.verify_student.views.log.error')
|
||||
@patch('lms.djangoapps.verify_student.utils.SailthruClient.send')
|
||||
def test_passed_status_template(self, mock_sailthru_send, mock_log_error):
|
||||
"""
|
||||
Test for verification passed.
|
||||
"""
|
||||
EmailMarketingConfiguration.objects.create(sailthru_verification_passed_template='test_template')
|
||||
data = {
|
||||
"EdX-ID": self.receipt_id,
|
||||
"Result": "PASS",
|
||||
@@ -1784,16 +1792,20 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase):
|
||||
attempt = SoftwareSecurePhotoVerification.objects.get(receipt_id=self.receipt_id)
|
||||
self.assertEqual(attempt.status, u'approved')
|
||||
self.assertEquals(response.content, 'OK!')
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertFalse(mock_log_error.called)
|
||||
self.assertTrue(mock_sailthru_send.call_args[1], 'test_template')
|
||||
|
||||
@mock.patch(
|
||||
@patch(
|
||||
'lms.djangoapps.verify_student.ssencrypt.has_valid_signature',
|
||||
mock.Mock(side_effect=mocked_has_valid_signature)
|
||||
)
|
||||
def test_fail_result(self):
|
||||
@patch('lms.djangoapps.verify_student.views.log.error')
|
||||
@patch('sailthru.sailthru_client.SailthruClient.send')
|
||||
def test_failed_status_template(self, mock_sailthru_send, mock_log_error):
|
||||
"""
|
||||
Test for failed verification.
|
||||
"""
|
||||
EmailMarketingConfiguration.objects.create(sailthru_verification_failed_template='test_template')
|
||||
data = {
|
||||
"EdX-ID": self.receipt_id,
|
||||
"Result": 'FAIL',
|
||||
@@ -1813,9 +1825,10 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase):
|
||||
self.assertEqual(attempt.error_code, u'Your photo doesn\'t meet standards.')
|
||||
self.assertEqual(attempt.error_msg, u'"Invalid photo"')
|
||||
self.assertEquals(response.content, 'OK!')
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertFalse(mock_log_error.called)
|
||||
self.assertTrue(mock_sailthru_send.call_args[1], 'test_template')
|
||||
|
||||
@mock.patch(
|
||||
@patch(
|
||||
'lms.djangoapps.verify_student.ssencrypt.has_valid_signature',
|
||||
mock.Mock(side_effect=mocked_has_valid_signature)
|
||||
)
|
||||
@@ -1841,7 +1854,7 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase):
|
||||
self.assertEqual(attempt.error_msg, u'"Memory overflow"')
|
||||
self.assertEquals(response.content, 'OK!')
|
||||
|
||||
@mock.patch(
|
||||
@patch(
|
||||
'lms.djangoapps.verify_student.ssencrypt.has_valid_signature',
|
||||
mock.Mock(side_effect=mocked_has_valid_signature)
|
||||
)
|
||||
@@ -1865,29 +1878,6 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase):
|
||||
)
|
||||
self.assertIn('Result Unknown not understood', response.content)
|
||||
|
||||
@mock.patch(
|
||||
'lms.djangoapps.verify_student.utils.send_mail',
|
||||
mock.Mock(side_effect=Exception())
|
||||
)
|
||||
def test_verification_status_email_not_sent(self):
|
||||
"""
|
||||
Test email is not sent in case of exception
|
||||
"""
|
||||
data = {
|
||||
"EdX-ID": self.receipt_id,
|
||||
"Result": "PASS",
|
||||
"Reason": "",
|
||||
"MessageType": "You have been verified."
|
||||
}
|
||||
json_data = json.dumps(data)
|
||||
self.client.post(
|
||||
reverse('verify_student_results_callback'), data=json_data,
|
||||
content_type='application/json',
|
||||
HTTP_AUTHORIZATION='test BBBBBBBBBBBBBBBBBBBB:testing',
|
||||
HTTP_DATE='testdate'
|
||||
)
|
||||
self.assertEqual(len(mail.outbox), 0)
|
||||
|
||||
|
||||
@attr(shard=2)
|
||||
class TestReverifyView(TestCase):
|
||||
|
||||
@@ -13,6 +13,7 @@ from django.utils.translation import ugettext as _
|
||||
|
||||
from edxmako.shortcuts import render_to_string
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from sailthru import SailthruClient
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -78,23 +79,18 @@ def verification_for_datetime(deadline, candidates):
|
||||
def send_verification_status_email(context):
|
||||
"""
|
||||
Send an email to inform learners about their verification status
|
||||
using sailthru
|
||||
"""
|
||||
subject = context['subject']
|
||||
message = render_to_string(context['message'], context['email_template_context'])
|
||||
from_address = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
|
||||
to_address = context['email']
|
||||
|
||||
try:
|
||||
send_mail(subject, message, from_address, [to_address], fail_silently=False)
|
||||
except: # pylint: disable=bare-except
|
||||
# We catch all exceptions and log them.
|
||||
# It would be much, much worse to roll back the transaction due to an uncaught
|
||||
# exception than to skip sending the notification email.
|
||||
log.exception(
|
||||
_("Could not send verification status email having subject: {subject} and email of user: {email}").format(
|
||||
subject=context['subject'],
|
||||
email=context['email']
|
||||
))
|
||||
sailthru_client = SailthruClient(context['email_config'].sailthru_key, context['email_config'].sailthru_secret)
|
||||
sailthru_response = sailthru_client.send(
|
||||
email=context['email'], template=context['template'],
|
||||
_vars=context['template_vars']
|
||||
)
|
||||
if not sailthru_response.is_ok():
|
||||
error = sailthru_response.get_error()
|
||||
log.error("Error attempting to send verification status email to user: {} via Sailthru: {}".format(
|
||||
context['email'], error.get_message()
|
||||
))
|
||||
|
||||
|
||||
def most_recent_verification(photo_id_verifications, sso_id_verifications, most_recent_key):
|
||||
|
||||
@@ -32,6 +32,7 @@ from pytz import UTC
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from edxmako.shortcuts import render_to_response, render_to_string
|
||||
from email_marketing.models import EmailMarketingConfiguration
|
||||
from lms.djangoapps.commerce.utils import EcommerceService, is_account_activation_requirement_disabled
|
||||
from lms.djangoapps.verify_student.image import InvalidImageData, decode_image_data
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, VerificationDeadline
|
||||
@@ -1156,43 +1157,50 @@ def results_callback(request):
|
||||
except SoftwareSecurePhotoVerification.DoesNotExist:
|
||||
log.error("Software Secure posted back for receipt_id %s, but not found", receipt_id)
|
||||
return HttpResponseBadRequest("edX ID {} not found".format(receipt_id))
|
||||
|
||||
user = attempt.user
|
||||
context = {
|
||||
email_config = EmailMarketingConfiguration.current()
|
||||
verification_status_email_vars = {
|
||||
'platform_name': settings.PLATFORM_NAME,
|
||||
}
|
||||
email_context = {
|
||||
'email_config': email_config,
|
||||
'email': user.email
|
||||
}
|
||||
email_template_context = {'platform_name': settings.PLATFORM_NAME}
|
||||
if result == "PASS":
|
||||
log.debug("Approving verification for %s", receipt_id)
|
||||
attempt.approve()
|
||||
status = "approved"
|
||||
expiry_date = datetime.date.today() + datetime.timedelta(
|
||||
days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"]
|
||||
)
|
||||
email_template_context['full_name'] = user.profile.name
|
||||
email_template_context['expiry_date'] = expiry_date.strftime("%m/%d/%Y")
|
||||
context['email_template_context'] = email_template_context
|
||||
context['subject'] = _("Your {platform_name} ID Verification Approved").format(
|
||||
platform_name=settings.PLATFORM_NAME
|
||||
)
|
||||
context['message'] = 'emails/successfull_verification_email.txt'
|
||||
send_verification_status_email(context)
|
||||
if email_config.sailthru_verification_passed_template:
|
||||
expiry_date = datetime.date.today() + datetime.timedelta(
|
||||
days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"]
|
||||
)
|
||||
verification_status_email_vars['expiry_date'] = expiry_date.strftime("%m/%d/%Y")
|
||||
verification_status_email_vars['subject'] = _("Your {platform_name} ID Verification Approved").format(
|
||||
platform_name=settings.PLATFORM_NAME
|
||||
)
|
||||
verification_status_email_vars['full_name'] = user.profile.name
|
||||
email_context['template'] = email_config.sailthru_verification_passed_template
|
||||
email_context['template_vars'] = verification_status_email_vars
|
||||
send_verification_status_email(email_context)
|
||||
|
||||
elif result == "FAIL":
|
||||
log.debug("Denying verification for %s", receipt_id)
|
||||
attempt.deny(json.dumps(reason), error_code=error_code)
|
||||
status = "denied"
|
||||
email_template_context['reason'] = reason
|
||||
email_template_context['reverify_url'] = reverse("verify_student_reverify")
|
||||
email_template_context['faq_url'] = configuration_helpers.get_value(
|
||||
'ID_VERIFICATION_SUPPORT_LINK',
|
||||
settings.SUPPORT_SITE_LINK
|
||||
)
|
||||
context['email_template_context'] = email_template_context
|
||||
context['subject'] = _("Your {platform_name} Verification Has Been Denied").format(
|
||||
platform_name=settings.PLATFORM_NAME
|
||||
)
|
||||
context['message'] = 'emails/failed_verification_email.txt'
|
||||
send_verification_status_email(context)
|
||||
if email_config.sailthru_verification_failed_template:
|
||||
verification_status_email_vars['reason'] = reason
|
||||
verification_status_email_vars['reverify_url'] = reverse("verify_student_reverify")
|
||||
verification_status_email_vars['faq_url'] = configuration_helpers.get_value(
|
||||
'ID_VERIFICATION_SUPPORT_LINK',
|
||||
settings.SUPPORT_SITE_LINK
|
||||
)
|
||||
verification_status_email_vars['subject'] = _("Your {platform_name} Verification Has Been Denied").format(
|
||||
platform_name=settings.PLATFORM_NAME
|
||||
)
|
||||
email_context['template'] = email_config.sailthru_verification_failed_template
|
||||
email_context['template_vars'] = verification_status_email_vars
|
||||
send_verification_status_email(email_context)
|
||||
|
||||
elif result == "SYSTEM FAIL":
|
||||
log.debug("System failure for %s -- resetting to must_retry", receipt_id)
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
|
||||
${_("Sorry! The photos you submitted for ID verification were not accepted, for the following reason(s):")}
|
||||
|
||||
${_("The photo(s) of you: {reason}").format(reason=reason)}
|
||||
|
||||
${_("Resubmit Verification: {reverify_url}").format(reverify_url=reverify_url)}
|
||||
$(_("ID Verification FAQ: {faq_url}").format(faq_url=faq_url)
|
||||
|
||||
${_("Thank you,")}
|
||||
${_("The {platform_name} team").format(platform_name=platform_name)}
|
||||
@@ -1,10 +0,0 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
|
||||
${_("Hi {full_name},").format(full_name=full_name)}
|
||||
|
||||
${_("Congratulations! Your ID verification process was successful.")}
|
||||
|
||||
${_("Your verification is effective for one year. It will expire on {expiry_date}").format(expiry_date=expiry_date)}
|
||||
|
||||
${_("Thank you,")}
|
||||
${_("The {platform_name} team").format(platform_name=platform_name)}
|
||||
Reference in New Issue
Block a user