Commit 66719b1b by Peter Fogg

Add financial assistance page.

Page skeleton and endpoint to submit FA requests to Zendesk.

ECOM-2824
ECOM-3038
parent 47b0d492
......@@ -8,7 +8,7 @@ import ddt
import json
import itertools
import unittest
from datetime import datetime
from datetime import datetime, timedelta
from HTMLParser import HTMLParser
from nose.plugins.attrib import attr
......@@ -32,6 +32,7 @@ from certificates import api as certs_api
from certificates.models import CertificateStatuses, CertificateGenerationConfiguration
from certificates.tests.factories import GeneratedCertificateFactory
from course_modes.models import CourseMode
from course_modes.tests.factories import CourseModeFactory
from courseware.model_data import set_score
from courseware.testutils import RenderXBlockTestMixin
from courseware.tests.factories import StudentModuleFactory
......@@ -190,7 +191,8 @@ class ViewsTestCase(ModuleStoreTestCase):
self.component = ItemFactory.create(category='problem', parent_location=self.vertical.location)
self.course_key = self.course.id
self.user = UserFactory(username='dummy', password='123456', email='test@mit.edu')
self.password = '123456'
self.user = UserFactory(username='dummy', password=self.password, email='test@mit.edu')
self.date = datetime(2013, 1, 22, tzinfo=UTC)
self.enrollment = CourseEnrollment.enroll(self.user, self.course_key)
self.enrollment.created = self.date
......@@ -270,7 +272,7 @@ class ViewsTestCase(ModuleStoreTestCase):
self.section.location.name,
'f'
])
self.client.login(username=self.user.username, password="123456")
self.client.login(username=self.user.username, password=self.password)
response = self.client.get(request_url)
self.assertEqual(response.status_code, 404)
......@@ -283,7 +285,7 @@ class ViewsTestCase(ModuleStoreTestCase):
self.section.location.name,
'1'
]
self.client.login(username=self.user.username, password="123456")
self.client.login(username=self.user.username, password=self.password)
for idx, val in enumerate(url_parts):
url_parts_copy = url_parts[:]
url_parts_copy[idx] = val + u'χ'
......@@ -458,6 +460,136 @@ class ViewsTestCase(ModuleStoreTestCase):
# Verify that the email opt-in checkbox does not appear
self.assertNotContains(response, checkbox_html, html=True)
def test_financial_assistance_page(self):
self.client.login(username=self.user.username, password=self.password)
url = reverse('financial_assistance')
response = self.client.get(url)
# This is a static page, so just assert that it is returned correctly
self.assertEqual(response.status_code, 200)
self.assertIn('Financial Assistance Application', response.content)
def test_financial_assistance_form(self):
non_verified_course = CourseFactory.create().id
verified_course_verified_track = CourseFactory.create().id
verified_course_audit_track = CourseFactory.create().id
verified_course_deadline_passed = CourseFactory.create().id
unenrolled_course = CourseFactory.create().id
enrollments = (
(non_verified_course, CourseMode.AUDIT, None),
(verified_course_verified_track, CourseMode.VERIFIED, None),
(verified_course_audit_track, CourseMode.AUDIT, None),
(verified_course_deadline_passed, CourseMode.AUDIT, datetime.now(UTC) - timedelta(days=1))
)
for course, mode, expiration in enrollments:
CourseModeFactory(mode_slug=CourseMode.AUDIT, course_id=course)
if course != non_verified_course:
CourseModeFactory(mode_slug=CourseMode.VERIFIED, course_id=course, expiration_datetime=expiration)
CourseEnrollmentFactory(course_id=course, user=self.user, mode=mode)
self.client.login(username=self.user.username, password=self.password)
url = reverse('financial_assistance_form')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
# Ensure that the user can only apply for assistance in
# courses which have a verified mode which hasn't expired yet,
# where the user is not already enrolled in verified mode
self.assertIn(str(verified_course_audit_track), response.content)
for course in (
non_verified_course,
verified_course_verified_track,
verified_course_deadline_passed,
unenrolled_course
):
self.assertNotIn(str(course), response.content)
def _submit_financial_assistance_form(self, data):
"""Submit a financial assistance request."""
self.client.login(username=self.user.username, password=self.password)
url = reverse('submit_financial_assistance_request')
return self.client.post(url, json.dumps(data), content_type='application/json')
@patch.object(views, '_record_feedback_in_zendesk')
def test_submit_financial_assistance_request(self, mock_record_feedback):
username = self.user.username
course = unicode(self.course_key)
legal_name = 'Jesse Pinkman'
country = 'United States'
income = '1234567890'
reason_for_applying = "It's just basic chemistry, yo."
goals = "I don't know if it even matters, but... work with my hands, I guess."
effort = "I'm done, okay? You just give me my money, and you and I, we're done."
data = {
'username': username,
'course_id': course,
'legal_name': legal_name,
'email': self.user.email,
'country': country,
'income': income,
'reason_for_applying': reason_for_applying,
'goals': goals,
'effort': effort,
'marketing_permission': False,
}
response = self._submit_financial_assistance_form(data)
self.assertEqual(response.status_code, 204)
__, ___, ticket_subject, ticket_body, tags, additional_info = mock_record_feedback.call_args[0]
for info in (country, income, reason_for_applying, goals, effort):
self.assertIn(info, ticket_body)
self.assertIn('This user HAS NOT allowed this content to be used for edX marketing purposes.', ticket_body)
self.assertEqual(
ticket_subject,
'Financial assistance request for user {username} in course {course}'.format(
username=username,
course=course
)
)
self.assertDictContainsSubset(
{
'issue_type': 'Financial Assistance',
'course_id': course
},
tags
)
self.assertIn('Client IP', additional_info)
@patch.object(views, '_record_feedback_in_zendesk', return_value=False)
def test_zendesk_submission_failed(self, _mock_record_feedback):
response = self._submit_financial_assistance_form({
'username': self.user.username,
'course_id': '',
'legal_name': '',
'email': '',
'country': '',
'income': '',
'reason_for_applying': '',
'goals': '',
'effort': '',
'marketing_permission': False,
})
self.assertEqual(response.status_code, 500)
@ddt.data(
({}, 400),
({'username': 'wwhite'}, 403)
)
@ddt.unpack
def test_submit_financial_assistance_errors(self, data, status):
response = self._submit_financial_assistance_form(data)
self.assertEqual(response.status_code, status)
def test_financial_assistance_login_required(self):
for url in (
reverse('financial_assistance'),
reverse('financial_assistance_form'),
reverse('submit_financial_assistance_request')
):
response = self.client.get(url)
self.assertRedirects(response, reverse('signin_user') + '?next=' + url)
@attr('shard_1')
# setting TIME_ZONE_DISPLAYED_FOR_DEADLINES explicitly
......
......@@ -2732,3 +2732,10 @@ PROCTORING_SETTINGS = {}
# The reason we introcuced this number is because we do not want the CCX
# to compete with the MOOC.
CCX_MAX_STUDENTS_ALLOWED = 200
# Financial assistance settings
# Maximum and minimum length of answers, in characters, for the
# financial assistance form
FINANCIAL_ASSISTANCE_MIN_LENGTH = 800
FINANCIAL_ASSISTANCE_MAX_LENGTH = 2500
......@@ -549,3 +549,6 @@ AUTHENTICATION_BACKENDS += ('lti_provider.users.LtiBackend',)
# ORGANIZATIONS
FEATURES['ORGANIZATIONS_APP'] = True
# Financial assistance page
FEATURES['ENABLE_FINANCIAL_ASSISTANCE_FORM'] = True
......@@ -53,6 +53,7 @@
@import 'views/shoppingcart';
@import 'views/homepage';
@import 'views/support';
@import "views/financial-assistance";
@import 'course/auto-cert';
// app - discussion
......
.financial-assistance-wrapper {
margin: auto;
padding: $baseline 0;
max-width: 1180px;
.financial-assistance {
border-bottom: 4px solid $gray-l5;
h1 {
@extend %t-title4;
@include text-align(left);
margin: 0;
padding: ($baseline/2) 0;
border-bottom: 4px solid $gray-l5;
color: $m-gray-d3;
}
h2 {
@extend %t-title6;
@extend %t-strong;
margin-top: ($baseline/2);
text-transform: none;
}
p {
@extend %t-copy-base;
padding: ($baseline/2) 0;
margin: 0;
color: $m-gray-d2;
}
.apply-form-list {
padding: 0;
list-style: none;
.apply-form-section {
border-bottom: 2px solid $gray-l5;
}
.apply-form-section:last-child {
border: none;
}
.about-me {
padding: 0;
list-style: none;
.about-me-item {
@include margin-right(150px);
display: inline-block;
p {
padding: 0;
display: block;
}
}
}
}
}
.financial-assistance-footer {
padding: $baseline;
.faq-link {
padding: $baseline/2;
}
.action-link {
@include float(right);
padding: $baseline/2;
background-color: $m-blue-d2;
color: $gray-l7;
border-radius: 2px;
}
}
}
<%inherit file="../main.html"/>
<%!
import json
from openedx.core.lib.js_utils import escape_json_dumps
%>
<%namespace name='static' file='/static_content.html'/>
<%block name="js_extra">
<%static:require_module module_name="js/financial-assistance/financial_assistance_form_factory" class_name="FinancialAssistanceFactory">
FinancialAssistanceFactory({
fields: ${escape_json_dumps(fields)},
user_details: ${escape_json_dumps(user_details)},
header_text: ${escape_json_dumps(header_text)},
student_faq_url: ${json.dumps(student_faq_url)},
dashboard_url: ${json.dumps(dashboard_url)},
platform_name: ${escape_json_dumps(platform_name)},
submit_url: ${json.dumps(submit_url)}
});
</%static:require_module>
</%block>
<div class="financial-assistance-wrapper"></div>
<%inherit file="../main.html"/>
<%
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from edxmako.shortcuts import marketing_link
%>
<div class="financial-assistance-wrapper">
<div class="financial-assistance financial-assistance-header">
<h1>${_("Financial Assistance Application")}</h1>
% for line in header_text:
<p>${line}</p>
% endfor
</div>
<div class="financial-assistance financial-assistance-body">
<h2>${_("A Note to Learners")}</h2>
<p>${_("Dear edX Learner,")}</p>
<p>${_("EdX Financial Assistance is a program we created to give learners in all financial circumstances a chance to earn a Verified Certificate upon successful completion of an edX course.")}</p>
<p>${_("If you are interested in working toward a Verified Certificate, but cannot afford to pay the fee, please apply now. Please note space is limited.")}</p>
<p>${_("In order to be eligible for edX Financial Assistance, you must demonstrate that paying the Verified Certificate fee would cause you economic hardship. To apply, you will be asked to answer a few questions about why you are applying and how the Verified Certificate will benefit you.")}</p>
<p>${_("Once your application is approved, we'll email to let you know and give you instructions for how to verify your identity on edX.org; then you can start working toward completing your edX course.")}</p>
<p>${_("EdX is committed to making it possible for you to take high quality courses from leading institutions regardless of your financial situation, earn a Verified Certificate, and share your success with others.")}</p>
<p class="signature">${_("Sincerely, Anant")}</p>
</div>
<div class="financial-assistance-footer">
<%
faq_link = marketing_link('FAQ')
%>
% if faq_link != '#':
<a class="faq-link" href="${faq_link}">${_("Back to Student FAQs")}</a>
% endif
<a class="action-link" href="${reverse('financial_assistance_form')}">${_("Apply for Financial Assistance")}</a>
</div>
</div>
......@@ -774,3 +774,22 @@ urlpatterns += (
urlpatterns += (
url(r'^api/', include('edx_proctoring.urls')),
)
if settings.FEATURES.get('ENABLE_FINANCIAL_ASSISTANCE_FORM'):
urlpatterns += (
url(
r'^financial-assistance/$',
'courseware.views.financial_assistance',
name='financial_assistance'
),
url(
r'^financial-assistance/apply/$',
'courseware.views.financial_assistance_form',
name='financial_assistance_form'
),
url(
r'^financial-assistance/submit/$',
'courseware.views.financial_assistance_request',
name='submit_financial_assistance_request'
)
)
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