Unsubscribe link added in bulk emails

This commit is contained in:
adeelehsan
2019-12-20 09:59:06 +05:00
parent 533adf6088
commit 12708a093d
16 changed files with 250 additions and 109 deletions

View File

@@ -12,6 +12,11 @@ from bulk_email.models_api import (
is_user_opted_out_for_course
)
from django.conf import settings
from django.urls import reverse
from lms.djangoapps.discussion.notification_prefs.views import UsernameCipher
def get_emails_enabled(user, course_id):
"""
@@ -28,3 +33,16 @@ def get_emails_enabled(user, course_id):
if is_bulk_email_feature_enabled(course_id=course_id):
return not is_user_opted_out_for_course(user=user, course_id=course_id)
return None
def get_unsubscribed_link(username, course_id):
"""
:param username: username
:param course_id:
:return: AES encrypted token based on the user email
"""
token = UsernameCipher.encrypt(username)
optout_url = reverse('bulk_email_opt_out', kwargs={'token': token, 'course_id': course_id})
url = '{base_url}{optout_url}'.format(base_url=settings.LMS_ROOT_URL, optout_url=optout_url)
return url

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -24,25 +24,25 @@
<tbody class='mcnTextBlockOuter'>
<tr>
<td valign='top' class='mcnTextBlockInner' style='border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;'>
<table align='left' border='0' cellpadding='0' cellspacing='0' width='366' class='mcnTextContentContainer' style='border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;'>
<tbody><tr>
<td valign='top' class='mcnTextContent' style='padding-top: 9px;padding-left: 18px;padding-bottom: 9px;padding-right: 0;border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;color: #606060;font-family: Helvetica;font-size: 11px;line-height: 125%;text-align: left;'>
<br>
</td>
</tr>
</tbody></table>
</td>
</tr>
</tbody>
</table></td>
</tr>
</table>
</td>
</td>
</tr>
</table>
<!-- // END PREHEADER -->
@@ -63,11 +63,11 @@
<table align='left' width='100%' border='0' cellpadding='0' cellspacing='0' class='mcnImageContentContainer' style='border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;'>
<tbody><tr>
<td class='mcnImageContent' valign='top' style='padding-right: 9px;padding-left: 9px;padding-top: 0;padding-bottom: 0;border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;'>
<a href='http://edx.org' title='' class='' target='_self' style='word-wrap: break-word !important;'>
<img align='left' alt='edX' src='http://courses.edx.org/static/images/bulk_email/edXHeaderImage.jpg' width='564.0000152587891' style='max-width: 600px;padding-bottom: 0;display: inline !important;vertical-align: bottom;border: 0;line-height: 100%;outline: none;text-decoration: none;height: auto !important;' class='mcnImage'>
</a>
</td>
</tr>
</tbody></table>
@@ -78,19 +78,19 @@
<tbody class='mcnTextBlockOuter'>
<tr>
<td valign='top' class='mcnTextBlockInner' style='border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;'>
<table align='left' border='0' cellpadding='0' cellspacing='0' width='599' class='mcnTextContentContainer' style='border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;'>
<tbody><tr>
<td valign='top' class='mcnTextContent' style='padding-top: 9px;padding-right: 18px;padding-bottom: 9px;padding-left: 18px;border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;color: #606060;font-family: Helvetica;font-size: 15px;line-height: 150%;text-align: left;'>
<div style='text-align: right;'>
<span style='font-size:11px;'><span style='color:#00a0e3;'>Connect with edX:</span></span> &nbsp;<a href='http://facebook.com/edxonline' target='_blank' style='color: #6DC6DD;font-weight: normal;text-decoration: underline;word-wrap: break-word !important;'><img align='none' height='16' src='http://courses.edx.org/static/images/bulk_email/FacebookIcon.png' style='width: 16px;height: 16px;border: 0;line-height: 100%;outline: none;text-decoration: none;' width='16'></a>&nbsp;&nbsp;<a href='http://twitter.com/edxonline' target='_blank' style='color: #6DC6DD;font-weight: normal;text-decoration: underline;word-wrap: break-word !important;'><img align='none' height='16' src='http://courses.edx.org/static/images/bulk_email/TwitterIcon.png' style='width: 16px;height: 16px;border: 0;line-height: 100%;outline: none;text-decoration: none;' width='16'></a>&nbsp;&nbsp;<a href='https://plus.google.com/108235383044095082735' target='_blank' style='color: #6DC6DD;font-weight: normal;text-decoration: underline;word-wrap: break-word !important;'><img align='none' height='16' src='http://courses.edx.org/static/images/bulk_email/GooglePlusIcon.png' style='width: 16px;height: 16px;border: 0;line-height: 100%;outline: none;text-decoration: none;' width='16'></a>&nbsp;&nbsp;<a href='http://www.meetup.com/edX-Communities/' target='_blank' style='color: #6DC6DD;font-weight: normal;text-decoration: underline;word-wrap: break-word !important;'><img align='none' height='16' src='http://courses.edx.org/static/images/bulk_email/MeetupIcon.png' style='width: 16px;height: 16px;border: 0;line-height: 100%;outline: none;text-decoration: none;' width='16'></a></div>
</td>
</tr>
</tbody></table>
</td>
</tr>
</tbody>
@@ -115,7 +115,7 @@
<tbody class='mcnCaptionBlockOuter'>
<tr>
<td class='mcnCaptionBlockInner' valign='top' style='padding: 9px;border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;'>
<table border='0' cellpadding='0' cellspacing='0' class='mcnCaptionLeftContentOuter' width='100%' style='border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;'>
<tbody><tr>
@@ -123,11 +123,11 @@
<table align='right' border='0' cellpadding='0' cellspacing='0' class='mcnCaptionLeftImageContentContainer' style='border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;'>
<tbody><tr>
<td class='mcnCaptionLeftImageContent' valign='top' style='border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;'>
<img alt='' src='{course_image_url}' width='176' style='max-width: 180px;border: 0;line-height: 100%;outline: none;text-decoration: none;vertical-align: bottom;height: auto !important;' class='mcnImage'>
</td>
</tr>
</tbody></table>
@@ -156,17 +156,17 @@
<tbody class='mcnTextBlockOuter'>
<tr>
<td valign='top' class='mcnTextBlockInner' style='border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;'>
<table align='left' border='0' cellpadding='0' cellspacing='0' width='600' class='mcnTextContentContainer' style='border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;'>
<tbody><tr>
<td valign='top' class='mcnTextContent' style='padding-top: 9px;padding-right: 18px;padding-bottom: 9px;padding-left: 18px;border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;color: #606060;font-family: Helvetica;font-size: 14px;line-height: 150%;text-align: left;'>
{{message_body}}
</td>
</tr>
</tbody></table>
</td>
</tr>
</tbody>
@@ -188,19 +188,19 @@
<tbody class='mcnTextBlockOuter'>
<tr>
<td valign='top' class='mcnTextBlockInner' style='border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;'>
<table align='left' border='0' cellpadding='0' cellspacing='0' width='600' class='mcnTextContentContainer' style='border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;'>
<tbody><tr>
<td valign='top' class='mcnTextContent' style='padding-top: 9px;padding-right: 18px;padding-bottom: 9px;padding-left: 18px;border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;color: #606060;font-family: Helvetica;font-size: 14px;line-height: 150%;text-align: left;'>
<div style='text-align: right;'>
<a href='http://facebook.com/edxonline' target='_blank' style='color: #2f73bc;font-weight: normal;text-decoration: underline;word-wrap: break-word !important;'><img align='none' height='16' src='http://courses.edx.org/static/images/bulk_email/FacebookIcon.png' style='width: 16px;height: 16px;border: 0;line-height: 100%;outline: none;text-decoration: none;' width='16'></a>&nbsp;&nbsp;<a href='http://twitter.com/edxonline' target='_blank' style='color: #2f73bc;font-weight: normal;text-decoration: underline;word-wrap: break-word !important;'><img align='none' height='16' src='http://courses.edx.org/static/images/bulk_email/TwitterIcon.png' style='width: 16px;height: 16px;border: 0;line-height: 100%;outline: none;text-decoration: none;' width='16'></a>&nbsp;&nbsp;<a href='https://plus.google.com/108235383044095082735' target='_blank' style='color: #2f73bc;font-weight: normal;text-decoration: underline;word-wrap: break-word !important;'><img align='none' height='16' src='http://courses.edx.org/static/images/bulk_email/GooglePlusIcon.png' style='width: 16px;height: 16px;border: 0;line-height: 100%;outline: none;text-decoration: none;' width='16'></a>&nbsp; &nbsp;<a href='http://www.meetup.com/edX-Communities/' target='_blank' style='color: #2f73bc;font-weight: normal;text-decoration: underline;word-wrap: break-word !important;'><img align='none' height='16' src='http://courses.edx.org/static/images/bulk_email/MeetupIcon.png' style='width: 16px;height: 16px;border: 0;line-height: 100%;outline: none;text-decoration: none;' width='16'></a></div>
</td>
</tr>
</tbody></table>
</td>
</tr>
</tbody>
@@ -225,12 +225,12 @@
<tbody class='mcnTextBlockOuter'>
<tr>
<td valign='top' class='mcnTextBlockInner' style='border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;'>
<table align='left' border='0' cellpadding='0' cellspacing='0' width='600' class='mcnTextContentContainer' style='border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;'>
<tbody><tr>
<td valign='top' class='mcnTextContent' style='padding-top: 9px;padding-right: 18px;padding-bottom: 9px;padding-left: 18px;border-collapse: collapse;mso-table-lspace: 0pt;mso-table-rspace: 0pt;color: #f2f2f2;font-family: Helvetica;font-size: 11px;line-height: 125%;text-align: left;'>
<em>Copyright © 2013 edX, All rights reserved.</em><br>
<br>
<br>
@@ -242,11 +242,12 @@
<br>
This email was automatically sent from {platform_name}. <br>
You are receiving this email at address {email} because you are enrolled in <a href='{course_url}'>{course_title}</a>.<br>
To stop receiving email like this, update your course email settings <a href='{email_settings_url}'>here</a>. <br>
To stop receiving email like this, update your course email settings <a href='{email_settings_url}'>here</a>. <br><br>
<a href='{unsubscribe_link}'>unsubscribe</a>
</td>
</tr>
</tbody></table>
</td>
</tr>
</tbody>

View File

@@ -41,6 +41,7 @@ from markupsafe import escape
from six import text_type
from bulk_email.models import CourseEmail, Optout
from bulk_email.api import get_unsubscribed_link
from lms.djangoapps.courseware.courses import get_course
from lms.djangoapps.instructor_task.models import InstructorTask
from lms.djangoapps.instructor_task.subtasks import (
@@ -187,7 +188,7 @@ def perform_delegate_email_batches(entry_id, course_id, task_input, action_name)
# inefficient OUTER JOIN query that would read the whole user table.
combined_set = recipient_qsets[0].union(*recipient_qsets[1:]) if len(recipient_qsets) > 1 \
else recipient_qsets[0]
recipient_fields = ['profile__name', 'email']
recipient_fields = ['profile__name', 'email', 'username']
log.info(u"Task %s: Preparing to queue subtasks for sending emails for course %s, email %s",
task_id, course_id, email_id)
@@ -502,6 +503,7 @@ def _send_course_email(entry_id, email_id, to_list, global_email_context, subtas
# use the CourseEmailTemplate that was associated with the CourseEmail
course_email_template = course_email.get_template()
try:
connection = get_connection()
connection.open()
@@ -537,6 +539,8 @@ def _send_course_email(entry_id, email_id, to_list, global_email_context, subtas
email_context['name'] = current_recipient['profile__name']
email_context['user_id'] = current_recipient['pk']
email_context['course_id'] = course_email.course_id
email_context['unsubscribe_link'] = get_unsubscribed_link(current_recipient['username'],
text_type(course_email.course_id))
# Construct message content using templates and context:
plaintext_msg = course_email_template.render_plaintext(course_email.text_message, email_context)

View File

@@ -23,6 +23,8 @@ from student.tests.factories import AdminFactory, CourseEnrollmentFactory, UserF
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from lms.djangoapps.bulk_email.api import get_unsubscribed_link
@patch('bulk_email.models.html_to_text', Mock(return_value='Mocking CourseEmail.text_message', autospec=True))
class TestOptoutCourseEmails(ModuleStoreTestCase):
@@ -87,6 +89,30 @@ class TestOptoutCourseEmails(ModuleStoreTestCase):
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].to[0], self.instructor.email)
def test_optout_using_unsubscribe_link_in_email(self):
"""
Make sure email is't sent to learner after opt out.
"""
self.client.logout()
self.client.login(username=self.instructor.username, password="test")
unsubscribe_link = get_unsubscribed_link(self.student.username, text_type(self.course.id))
response = self.client.post(unsubscribe_link, {'unsubscribe': True})
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'You have successfully unsubscribed from')
test_email = {
'action': 'Send email',
'send_to': '["myself", "learners"]',
'subject': 'Checking unsubscribe link in email',
'message': 'test message for all'
}
response = self.client.post(self.send_mail_url, test_email)
self.assertEqual(json.loads(response.content.decode('utf-8')), self.success_content)
self.assertEqual(len(mail.outbox), 1)
def test_optin_course(self):
"""
Make sure student receives course email after opting in.

View File

@@ -24,6 +24,7 @@ from mock import Mock, patch
from bulk_email.models import BulkEmailFlag, Optout
from bulk_email.tasks import _get_course_email_context, _get_source_address
from course_modes.models import CourseMode
from lms.djangoapps.courseware.tests.factories import InstructorFactory, StaffFactory
from lms.djangoapps.instructor_task.subtasks import update_subtask_status
from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort
@@ -603,6 +604,28 @@ class TestEmailSendFromDashboardMockedHtmlToText(EmailSendFromDashboardTestCase)
[s.email for s in added_users if s not in optouts])
six.assertCountEqual(self, outbox_contents, should_send_contents)
def test_unsubscribe_link_in_email(self):
"""
Make sure opt out link is present in email.
"""
test_email = {
'action': 'Send email',
'send_to': '["learners"]',
'subject': 'Checking unsubscribe link in email',
'message': 'test message for all'
}
response = self.client.post(self.send_mail_url, test_email)
self.assertEqual(json.loads(response.content.decode('utf-8')), self.success_content)
# check unsubscribe link in template
for m in mail.outbox:
plain_template = m.body
html_template = m.alternatives[0][0]
assert u'bulk_email/email/optout/' in plain_template
assert u'bulk_email/email/optout/' in html_template
@skipIf(os.environ.get("TRAVIS") == 'true', "Skip this test in Travis CI.")
class TestEmailSendFromDashboard(EmailSendFromDashboardTestCase):

View File

@@ -188,6 +188,7 @@ class CourseEmailTemplateTest(TestCase):
'email_settings_url': "/location/of/email/settings/url",
'platform_name': 'edX',
'email': 'your-email@test.com',
'unsubscribe_link': '/bulk_email/email/optout/dummy'
}
return context

View File

@@ -2,7 +2,6 @@
"""
Test the bulk email opt out view.
"""
from six import text_type
import ddt
from django.http import Http404
@@ -12,6 +11,8 @@ from django.urls import reverse
from bulk_email.models import Optout
from bulk_email.views import opt_out_email_updates
from six import text_type
from lms.djangoapps.discussion.notification_prefs.views import UsernameCipher
from openedx.core.lib.tests import attr
from student.tests.factories import UserFactory
@@ -28,10 +29,10 @@ class OptOutEmailUpdatesViewTest(ModuleStoreTestCase):
"""
def setUp(self):
super(OptOutEmailUpdatesViewTest, self).setUp()
self.user = UserFactory.create(username="testuser1")
self.user = UserFactory.create(username="testuser1", email='test@example.com')
self.course = CourseFactory.create(run='testcourse1', display_name='Test Course Title')
self.token = UsernameCipher.encrypt('testuser1')
self.request_factory = RequestFactory()
self.course = CourseFactory.create(run='testcourse1', display_name='Test Course Title')
self.url = reverse('bulk_email_opt_out', args=[self.token, text_type(self.course.id)])
# Ensure we start with no opt-out records
@@ -42,28 +43,28 @@ class OptOutEmailUpdatesViewTest(ModuleStoreTestCase):
Ensure that the default GET view asks for confirmation.
"""
response = self.client.get(self.url)
self.assertContains(response, "Do you want to unsubscribe from emails for Test Course Title?")
self.assertContains(response, "confirm unsubscribe from")
self.assertEqual(Optout.objects.count(), 0)
def test_opt_out_email_unsubscribe(self):
"""
Ensure that the POSTing "confirm" creates the opt-out record.
"""
response = self.client.post(self.url, {'submit': 'confirm'})
self.assertContains(response, "You have been unsubscribed from emails for Test Course Title.")
response = self.client.post(self.url, {'unsubscribe': True})
self.assertContains(response, "You have successfully unsubscribed from")
self.assertEqual(Optout.objects.count(), 1)
def test_opt_out_email_cancel(self):
"""
Ensure that the POSTing "cancel" does not create the opt-out record
"""
response = self.client.post(self.url, {'submit': 'cancel'})
self.assertContains(response, "You have not been unsubscribed from emails for Test Course Title.")
response = self.client.post(self.url)
self.assertContains(response, "You have not been unsubscribed from")
self.assertEqual(Optout.objects.count(), 0)
@ddt.data(
("ZOMG INVALID BASE64 CHARS!!!", "base64url", False),
("Non-ASCII\xff", "base64url", False),
("Non-ASCII\xff".encode(), "base64url", False),
("D6L8Q01ztywqnr3coMOlq0C3DG05686lXX_1ArEd0ok", "base64url", False),
("AAAAAAAAAAA=", "initialization_vector", False),
("nMXVK7PdSlKPOovci-M7iqS09Ux8VoCNDJixLBmj", "aes", False),

View File

@@ -2,8 +2,8 @@
URLs for bulk_email app
"""
from django.conf import settings
from django.conf.urls import url
from django.conf import settings
from bulk_email import views

View File

@@ -35,8 +35,9 @@ def opt_out_email_updates(request, token, course_id):
Raises a 404 if there are any errors parsing the input.
"""
try:
username = UsernameCipher().decrypt(token.encode())
username = UsernameCipher().decrypt(token)
user = User.objects.get(username=username)
course_key = CourseKey.from_string(course_id)
course = get_course_by_id(course_key, depth=0)
@@ -49,23 +50,22 @@ def opt_out_email_updates(request, token, course_id):
except InvalidKeyError:
raise Http404("course")
unsub_check = request.POST.get('unsubscribe', False)
context = {
'course': course,
'cancelled': False,
'confirmed': False,
'unsubscribe': unsub_check
}
if request.method == 'POST':
if request.POST.get('submit') == 'confirm':
Optout.objects.get_or_create(user=user, course_id=course.id)
log.info(
u"User %s (%s) opted out of receiving emails from course %s",
user.username,
user.email,
course_id,
)
context['confirmed'] = True
else:
context['cancelled'] = True
if request.method == 'GET':
return render_to_response('bulk_email/confirm_unsubscribe.html', context)
return render_to_response('bulk_email/unsubscribe.html', context)
if request.method == 'POST' and unsub_check:
Optout.objects.get_or_create(user=user, course_id=course_key)
log.info(
u"User %s (%s) opted out of receiving emails from course %s",
user.username,
user.email,
course_id,
)
return render_to_response('bulk_email/unsubscribe_success.html', context)

View File

@@ -22,3 +22,26 @@
width: flex-grid(6);
}
}
.container.unsubscribe-bulk-email {
max-width: 400px;
min-width: 320px;
margin: 0 auto;
padding: ($baseline*3) 15px ($baseline*6);
h1 {
@include text-align(left);
font-size: 30px;
}
.button {
background-color: #23419f;
border-color: #23419f;
border: 2px solid;
border-radius: 25px;
text-align: center;
width: 100%;
}
}

View File

@@ -0,0 +1,46 @@
<%page expression_filter="h"/>
<%!
from openedx.core.djangolib.markup import Text, HTML
from django.utils.translation import ugettext as _
%>
<%inherit file="../main.html" />
<%namespace name='static' file='../static_content.html'/>
<%block name="pagetitle">${Text(_("Unsubscribe"))}</%block>
<section class="container unsubscribe-bulk-email">
<section>
<h1>
<%block name="pageheader">${Text(_("Unsubscribe"))} </%block>
</h1>
<form class="message" action="" method="POST">
<%block name="pagecontent">
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }">
<input type="checkbox" name="unsubscribe" checked>
${Text(_("confirm unsubscribe from {org} {course} emails.")).format(
org=HTML("<strong>{}'s</strong>").format(course.display_org_with_default),
course=HTML("<strong>{}</strong>").format(course.display_name_with_default)
)
}
<br><br>
<p>
<strong>${_("Note:")}</strong>
${_("You will still receive course emails from other courses you are enrolled in.")}
</p>
<br><br>
<input class="button" type="submit" value="Confirm">
</%block>
</form>
</section>
</section>

View File

@@ -1,48 +0,0 @@
<%page expression_filter="h" />
<%inherit file="../main.html" />
<%!
from openedx.core.djangolib.markup import Text
from django.utils.translation import ugettext as _
%>
<%def name="header()">
%if confirmed:
${Text(_("Unsubscribe Successful"))}
%elif cancelled:
${Text(_("Unsubscribe Cancelled"))}
%else:
${Text(_("Confirm Unsubscribe"))}
%endif
</%def>
<%block name="pagetitle">${header()}</%block>
<section class="container unsubscribe">
<section class="message">
<h1>
<%block name="pageheader">${header()}</%block>
</h1>
<p>
<%block name="pagecontent">
%if confirmed:
${Text(_("You have been unsubscribed from emails for {course}.")).format(
course=course.display_name_with_default
)}
%elif cancelled:
${Text(_("You have not been unsubscribed from emails for {course}.")).format(
course=course.display_name_with_default
)}
%else:
${Text(_("Do you want to unsubscribe from emails for {course}?")).format(
course=course.display_name_with_default
)}
<br /><br />
<form method="post">
<input type="hidden" id="csrf_token" name="csrfmiddlewaretoken" value="${csrf_token}">
<button name="submit" value="confirm" type="submit">${Text(_('Unsubscribe'))}</button>
<button name="submit" value="cancel" type="submit">${Text(_('Cancel'))}</button>
</form>
%endif
</%block>
</p>
</section>
</section>

View File

@@ -0,0 +1,46 @@
<%page expression_filter="h"/>
<%!
from openedx.core.djangolib.markup import HTML, Text
from django.utils.translation import ugettext as _
%>
<%inherit file="../main.html" />
<%namespace name='static' file='../static_content.html'/>
<%block name="pagetitle">${Text(_("Unsubscribe"))}</%block>
<section class="container unsubscribe-bulk-email">
<section class="message">
<h1>
<%block name="pageheader">${Text(_("Unsubscribe"))} </%block>
</h1>
<%block name="pagecontent">
%if unsubscribe:
${Text(_("You have successfully unsubscribed from {org} {course} emails.")).format(
org=HTML("<strong>{}'s</strong>").format(course.display_org_with_default),
course=HTML("<strong>{}</strong>").format(course.display_name_with_default)
)}
%else:
${Text(_("You have not been unsubscribed from {org} {course} emails.")).format(
org=HTML("<strong>{}'s</strong>").format(course.display_org_with_default),
course=HTML("<strong>{}</strong>").format(course.display_name_with_default)
)}
%endif
<br><br>
${Text(_("{link_start}Return to edX.org{link_end}")).format(
link_start=HTML("<u> <a href='https://www.edx.org/'>"),
link_end=HTML("</a> </u> "),
)}
</%block>
</section>
</section>