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:
uzairr
2018-05-07 12:30:47 +05:00
parent 335696a188
commit dfb295fe38
7 changed files with 112 additions and 98 deletions

View File

@@ -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),
),
]

View File

@@ -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,

View File

@@ -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):

View File

@@ -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):

View File

@@ -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)

View File

@@ -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)}

View File

@@ -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)}