Commit f5767a96 by Renzo Lucioni

Prep marketing iframe and relevant courseware view for email opt-in

Feature flagged. Puts a checkbox in the iframe. The iframe uses an organization_full_name parameter forwarded from Drupal by the courseware views and POSTs an email_opt_in parameter to the student views, preserving it on 403.
parent eacd5256
......@@ -20,8 +20,9 @@ except Exception:
cache = cache.cache
def cache_if_anonymous(view_func):
"""
def cache_if_anonymous(*get_parameters):
"""Cache a page for anonymous users.
Many of the pages in edX are identical when the user is not logged
in, but should not be cached when the user is logged in (because
of the navigation bar at the top with the username).
......@@ -31,32 +32,46 @@ def cache_if_anonymous(view_func):
the cookie to the vary header, and so every page is cached seperately
for each user (because each user has a different csrf token).
Optionally, provide a series of GET parameters as arguments to cache
pages with these GET parameters separately.
Note that this decorator should only be used on views that do not
contain the csrftoken within the html. The csrf token can be included
in the header by ordering the decorators as such:
@ensure_csrftoken
@cache_if_anonymous
@cache_if_anonymous()
def myView(request):
"""
@wraps(view_func)
def _decorated(request, *args, **kwargs):
if not request.user.is_authenticated():
#Use the cache
# same view accessed through different domain names may
# return different things, so include the domain name in the key.
domain = str(request.META.get('HTTP_HOST')) + '.'
cache_key = domain + "cache_if_anonymous." + get_language() + '.' + request.path
response = cache.get(cache_key)
if not response:
response = view_func(request, *args, **kwargs)
cache.set(cache_key, response, 60 * 3)
return response
else:
#Don't use the cache
return view_func(request, *args, **kwargs)
return _decorated
def decorator(view_func):
"""The outer wrapper, used to allow the decorator to take optional
arguments.
"""
@wraps(view_func)
def wrapper(request, *args, **kwargs):
"""The inner wrapper, which wraps the view function."""
if not request.user.is_authenticated():
#Use the cache
# same view accessed through different domain names may
# return different things, so include the domain name in the key.
domain = str(request.META.get('HTTP_HOST')) + '.'
cache_key = domain + "cache_if_anonymous." + get_language() + '.' + request.path
# Include the values of GET parameters in the cache key.
for get_parameter in get_parameters:
cache_key = cache_key + '.' + unicode(request.GET.get(get_parameter))
response = cache.get(cache_key) # pylint: disable=maybe-no-member
if not response:
response = view_func(request, *args, **kwargs)
cache.set(cache_key, response, 60 * 3) # pylint: disable=maybe-no-member
return response
else:
#Don't use the cache
return view_func(request, *args, **kwargs)
return wrapper
return decorator
......@@ -33,7 +33,7 @@ def get_course_enrollments(user):
@ensure_csrf_cookie
@cache_if_anonymous
@cache_if_anonymous()
def index(request):
'''
Redirects to main page -- info page if user authenticated, or marketing if not
......@@ -81,7 +81,7 @@ def index(request):
@ensure_csrf_cookie
@cache_if_anonymous
@cache_if_anonymous()
def courses(request):
"""
Render the "find courses" page. If the marketing site is enabled, redirect
......
......@@ -3,6 +3,7 @@
Tests courseware views.py
"""
import unittest
import cgi
from datetime import datetime
from mock import MagicMock, patch, create_autospec
......@@ -99,6 +100,10 @@ class ViewsTestCase(TestCase):
chapter = 'Overview'
self.chapter_url = '%s/%s/%s' % ('/courses', self.course_key, chapter)
# For marketing email opt-in
self.organization_full_name = u"𝖀𝖒𝖇𝖗𝖊𝖑𝖑𝖆 𝕮𝖔𝖗𝖕𝖔𝖗𝖆𝖙𝖎𝖔𝖓"
self.organization_html = "<p>'+Umbrella/Corporation+'</p>"
@unittest.skipUnless(settings.FEATURES.get('ENABLE_SHOPPING_CART'), "Shopping Cart not enabled in settings")
@patch.dict(settings.FEATURES, {'ENABLE_PAID_COURSE_REGISTRATION': True})
def test_course_about_in_cart(self):
......@@ -256,17 +261,26 @@ class ViewsTestCase(TestCase):
# generate/store a real password.
self.assertEqual(chat_settings['password'], "johndoe@%s" % domain)
@patch.dict(settings.FEATURES, {'ENABLE_MKTG_EMAIL_OPT_IN': True})
def test_course_mktg_about_coming_soon(self):
# we should not be able to find this course
# We should not be able to find this course
url = reverse('mktg_about_course', kwargs={'course_id': 'no/course/here'})
response = self.client.get(url)
response = self.client.get(url, {'organization_full_name': self.organization_full_name})
self.assertIn('Coming Soon', response.content)
# Verify that the checkbox is not displayed
self._email_opt_in_checkbox(response)
@patch.dict(settings.FEATURES, {'ENABLE_MKTG_EMAIL_OPT_IN': True})
def test_course_mktg_register(self):
response = self._load_mktg_about()
response = self._load_mktg_about(organization_full_name=self.organization_full_name)
self.assertIn('Enroll in', response.content)
self.assertNotIn('and choose your student track', response.content)
# Verify that the checkbox is displayed
self._email_opt_in_checkbox(response, self.organization_full_name)
@patch.dict(settings.FEATURES, {'ENABLE_MKTG_EMAIL_OPT_IN': True})
def test_course_mktg_register_multiple_modes(self):
CourseMode.objects.get_or_create(
mode_slug='honor',
......@@ -279,12 +293,42 @@ class ViewsTestCase(TestCase):
course_id=self.course_key
)
response = self._load_mktg_about()
response = self._load_mktg_about(organization_full_name=self.organization_full_name)
self.assertIn('Enroll in', response.content)
self.assertIn('and choose your student track', response.content)
# Verify that the checkbox is displayed
self._email_opt_in_checkbox(response, self.organization_full_name)
# clean up course modes
CourseMode.objects.all().delete()
@patch.dict(settings.FEATURES, {'ENABLE_MKTG_EMAIL_OPT_IN': True})
def test_course_mktg_no_organization_name(self):
# Don't pass an organization name as a GET parameter, even though the email
# opt-in feature is enabled.
response = response = self._load_mktg_about()
# Verify that the checkbox is not displayed
self._email_opt_in_checkbox(response)
@patch.dict(settings.FEATURES, {'ENABLE_MKTG_EMAIL_OPT_IN': False})
def test_course_mktg_opt_in_disabled(self):
# Pass an organization name as a GET parameter, even though the email
# opt-in feature is disabled.
response = self._load_mktg_about(organization_full_name=self.organization_full_name)
# Verify that the checkbox is not displayed
self._email_opt_in_checkbox(response)
@patch.dict(settings.FEATURES, {'ENABLE_MKTG_EMAIL_OPT_IN': True})
def test_course_mktg_organization_html(self):
response = self._load_mktg_about(organization_full_name=self.organization_html)
# Verify that the checkbox is displayed with the organization name
# in the label escaped as expected.
self._email_opt_in_checkbox(response, cgi.escape(self.organization_html))
@patch.dict(settings.FEATURES, {'IS_EDX_DOMAIN': True})
def test_mktg_about_language_edx_domain(self):
# Since we're in an edx-controlled domain, and our marketing site
......@@ -340,9 +384,8 @@ class ViewsTestCase(TestCase):
response = self.client.get(url)
self.assertFalse('<script>' in response.content)
def _load_mktg_about(self, language=None):
"""
Retrieve the marketing about button (iframed into the marketing site)
def _load_mktg_about(self, language=None, organization_full_name=None):
"""Retrieve the marketing about button (iframed into the marketing site)
and return the HTTP response.
Keyword Args:
......@@ -362,7 +405,22 @@ class ViewsTestCase(TestCase):
headers['HTTP_ACCEPT_LANGUAGE'] = language
url = reverse('mktg_about_course', kwargs={'course_id': unicode(self.course_key)})
return self.client.get(url, **headers)
if organization_full_name:
return self.client.get(url, {'organization_full_name': organization_full_name}, **headers)
else:
return self.client.get(url, **headers)
def _email_opt_in_checkbox(self, response, organization_full_name=None):
"""Check if the email opt-in checkbox appears in the response content."""
checkbox_html = '<input id="email-opt-in" type="checkbox" name="opt-in" class="email-opt-in" value="true" checked>'
if organization_full_name:
# Verify that the email opt-in checkbox appears, and that the expected
# organization name is displayed.
self.assertContains(response, checkbox_html, html=True)
self.assertContains(response, organization_full_name)
else:
# Verify that the email opt-in checkbox does not appear
self.assertNotContains(response, checkbox_html, html=True)
# setting TIME_ZONE_DISPLAYED_FOR_DEADLINES explicitly
......
......@@ -5,6 +5,7 @@ Courseware views functions
import logging
import urllib
import json
import cgi
from datetime import datetime
from collections import defaultdict
......@@ -93,7 +94,7 @@ def user_groups(user):
@ensure_csrf_cookie
@cache_if_anonymous
@cache_if_anonymous()
def courses(request):
"""
Render "find courses" page. The course selection work is done in courseware.courses.
......@@ -713,7 +714,7 @@ def registered_for_course(course, user):
@ensure_csrf_cookie
@cache_if_anonymous
@cache_if_anonymous()
def course_about(request, course_id):
"""
Display the course's about page.
......@@ -802,13 +803,10 @@ def course_about(request, course_id):
@ensure_csrf_cookie
@cache_if_anonymous
@cache_if_anonymous('organization_full_name')
@ensure_valid_course_key
def mktg_course_about(request, course_id):
"""
This is the button that gets put into an iframe on the Drupal site
"""
"""This is the button that gets put into an iframe on the Drupal site."""
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
try:
......@@ -818,8 +816,7 @@ def mktg_course_about(request, course_id):
)
course = get_course_with_access(request.user, permission_name, course_key)
except (ValueError, Http404):
# if a course does not exist yet, display a coming
# soon button
# If a course does not exist yet, display a "Coming Soon" button
return render_to_response(
'courseware/mktg_coming_soon.html', {'course_id': course_key.to_deprecated_string()}
)
......@@ -846,6 +843,12 @@ def mktg_course_about(request, course_id):
'course_modes': course_modes,
}
if settings.FEATURES.get('ENABLE_MKTG_EMAIL_OPT_IN'):
# Drupal will pass the organization's full name as a GET parameter. If no full name
# is provided, the marketing iframe won't show the email opt-in checkbox.
organization_full_name = request.GET.get('organization_full_name')
context['organization_full_name'] = cgi.escape(organization_full_name) if organization_full_name else organization_full_name
# The edx.org marketing site currently displays only in English.
# To avoid displaying a different language in the register / access button,
# we force the language to English.
......
......@@ -30,7 +30,7 @@ def index(request, template):
@ensure_csrf_cookie
@cache_if_anonymous
@cache_if_anonymous()
def render(request, template):
"""
This view function renders the template sent without checking that it
......@@ -43,7 +43,7 @@ def render(request, template):
@ensure_csrf_cookie
@cache_if_anonymous
@cache_if_anonymous()
def render_press_release(request, slug):
"""
Render a press release given a slug. Similar to the "render" function above,
......
......@@ -283,6 +283,9 @@ FEATURES = {
# Enable the combined login/registration form
'ENABLE_COMBINED_LOGIN_REGISTRATION': False,
# Enable organizational email opt-in
'ENABLE_MKTG_EMAIL_OPT_IN': False,
# Show a section in the membership tab of the instructor dashboard
# to allow an upload of a CSV file that contains a list of new accounts to create
# and register for course.
......
......@@ -16,6 +16,14 @@
<script type="text/javascript">
(function() {
$(".register").click(function(event) {
if ( !$("#email-opt-in").prop("checked") ) {
$("input[name='email_opt_in']").val("false");
}
var email_opt_in = $("input[name='email_opt_in']").val(),
current_href = $("a.register").attr("href");
$("a.register").attr("href", current_href + "&email_opt_in=" + email_opt_in)
$("#class_enroll_form").submit();
event.preventDefault();
});
......@@ -29,7 +37,9 @@
window.top.location.href = "${reverse('dashboard')}";
}
} else if (xhr.status == 403) {
window.top.location.href = $("a.register").attr("href") || "${reverse('register_user')}?course_id=${course.id | u}&enrollment_action=enroll";
var email_opt_in = $("input[name='email_opt_in']").val();
## Ugh.
window.top.location.href = $("a.register").attr("href") || "${reverse('register_user')}?course_id=${course.id | u}&enrollment_action=enroll&email_opt_in=" + email_opt_in;
} else {
$('#register_error').html(
(xhr.responseText ? xhr.responseText : "${_("An error occurred. Please try again later.")}")
......@@ -71,6 +81,23 @@
</span>
%endif
</a>
% if settings.FEATURES.get('ENABLE_MKTG_EMAIL_OPT_IN'):
## We only display the email opt-in checkbox if we've been given a valid full name (i.e., not None)
% if organization_full_name:
<p class="form-field">
<input id="email-opt-in" type="checkbox" name="opt-in" class="email-opt-in" value="true" checked>
<label for="email-opt-in">
## Translators: This line appears next a checkbox which users can leave checked or uncheck in order
## to indicate whether they want to receive emails from the organization offering the course.
${_("I would like to receive email about other {organization_full_name} programs and offers.").format(
organization_full_name=organization_full_name
)}
</label>
</p>
% endif
% endif
%else:
<div class="action registration-closed is-disabled">${_("Enrollment Is Closed")}</div>
%endif
......@@ -83,6 +110,7 @@
<fieldset class="enroll_fieldset">
<input name="course_id" type="hidden" value="${course.id | h}">
<input name="enrollment_action" type="hidden" value="enroll">
<input name="email_opt_in" type="hidden" value="true">
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }">
</fieldset>
<div class="submit">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment