feat: use new flow with with financial assistance form
This commit is contained in:
@@ -127,21 +127,7 @@ class TestFinancialAssistanceViews(TestCase):
|
||||
assert has_application is False
|
||||
assert reason == response_data.get('content').get('message')
|
||||
|
||||
@ddt.data(
|
||||
{
|
||||
'status': status.HTTP_400_BAD_REQUEST,
|
||||
'content': {'message': 'Invalid course id provided'},
|
||||
'message': 'Invalid course id provided',
|
||||
'created': False
|
||||
},
|
||||
{
|
||||
'status': status.HTTP_200_OK,
|
||||
'content': {'success': True},
|
||||
'message': None,
|
||||
'created': True
|
||||
}
|
||||
)
|
||||
def test_create_financial_assistance_application(self, response_data):
|
||||
def test_create_financial_assistance_application(self):
|
||||
"""
|
||||
Tests the functionality of create_financial_assistance_application which calls edx-financial-assistance backend
|
||||
to create a new financial assistance application given a form data.
|
||||
@@ -149,10 +135,26 @@ class TestFinancialAssistanceViews(TestCase):
|
||||
test_form_data = {
|
||||
'lms_user_id': self.user.id,
|
||||
'course_id': self.test_course_id,
|
||||
'income': '85K_TO_100K'
|
||||
'income': '$85,000 - $100,000'
|
||||
}
|
||||
with patch.object(OAuthAPIClient, 'request') as oauth_mock:
|
||||
oauth_mock.return_value = self._mock_response(response_data.get('status'), response_data.get('content'))
|
||||
created, message = create_financial_assistance_application(form_data=test_form_data)
|
||||
assert created is response_data.get('created')
|
||||
assert message == response_data.get('message')
|
||||
oauth_mock.return_value = self._mock_response(status.HTTP_200_OK, {'success': True})
|
||||
response = create_financial_assistance_application(form_data=test_form_data)
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
|
||||
def test_create_financial_assistance_application_bad_request(self):
|
||||
"""
|
||||
Tests the functionality of create_financial_assistance_application which calls edx-financial-assistance backend
|
||||
to create a new financial assistance application given a form data.
|
||||
"""
|
||||
test_form_data = {
|
||||
'lms_user_id': self.user.id,
|
||||
'course_id': 'invalid_course_id',
|
||||
'income': '$85,000 - $100,000'
|
||||
}
|
||||
error_response = {'message': 'Invalid course id provided'}
|
||||
with patch.object(OAuthAPIClient, 'request') as oauth_mock:
|
||||
oauth_mock.return_value = self._mock_response(status.HTTP_400_BAD_REQUEST, error_response)
|
||||
response = create_financial_assistance_application(form_data=test_form_data)
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
assert json.loads(response.content.decode('utf-8')) == error_response
|
||||
|
||||
@@ -13,13 +13,12 @@ from urllib.parse import urlencode
|
||||
from uuid import uuid4
|
||||
|
||||
import ddt
|
||||
from capa.tests.response_xml_factory import \
|
||||
MultipleChoiceResponseXMLFactory
|
||||
from capa.tests.response_xml_factory import MultipleChoiceResponseXMLFactory
|
||||
from completion.test_utils import CompletionWaffleTestMixin
|
||||
from crum import set_current_request
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.http import Http404, HttpResponseBadRequest
|
||||
from django.http import Http404, HttpResponse, HttpResponseBadRequest
|
||||
from django.http.request import QueryDict
|
||||
from django.test import RequestFactory, TestCase
|
||||
from django.test.client import Client
|
||||
@@ -31,24 +30,17 @@ from markupsafe import escape
|
||||
from milestones.tests.utils import MilestonesTestCaseMixin
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
from pytz import UTC
|
||||
from rest_framework import status
|
||||
from web_fragments.fragment import Fragment
|
||||
from xblock.core import XBlock
|
||||
from xblock.fields import Scope, String
|
||||
from xmodule.course_module import (
|
||||
COURSE_VISIBILITY_PRIVATE,
|
||||
COURSE_VISIBILITY_PUBLIC,
|
||||
COURSE_VISIBILITY_PUBLIC_OUTLINE
|
||||
)
|
||||
from xmodule.course_module import COURSE_VISIBILITY_PRIVATE, COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE
|
||||
from xmodule.data import CertificatesDisplayBehaviors
|
||||
from xmodule.graders import ShowCorrectness
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.tests.django_utils import CourseUserType, ModuleStoreTestCase, SharedModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import (
|
||||
CourseFactory,
|
||||
ItemFactory,
|
||||
check_mongo_calls
|
||||
)
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, check_mongo_calls
|
||||
|
||||
import lms.djangoapps.courseware.views.views as views
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
@@ -957,9 +949,9 @@ class ViewsTestCase(BaseViewsTestCase):
|
||||
|
||||
self.assertContains(response, str(course))
|
||||
|
||||
def _submit_financial_assistance_form(self, data):
|
||||
def _submit_financial_assistance_form(self, data, submit_url='submit_financial_assistance_request'):
|
||||
"""Submit a financial assistance request."""
|
||||
url = reverse('submit_financial_assistance_request')
|
||||
url = reverse(submit_url)
|
||||
return self.client.post(url, json.dumps(data), content_type='application/json')
|
||||
|
||||
@patch.object(views, 'create_zendesk_ticket', return_value=200)
|
||||
@@ -1020,15 +1012,33 @@ class ViewsTestCase(BaseViewsTestCase):
|
||||
})
|
||||
assert response.status_code == 500
|
||||
|
||||
@patch.object(
|
||||
views, 'create_financial_assistance_application', return_value=HttpResponse(status=status.HTTP_204_NO_CONTENT)
|
||||
)
|
||||
def test_submit_financial_assistance_request_v2(self, create_application_mock):
|
||||
form_data = {
|
||||
'username': self.user.username,
|
||||
'course': 'course-v1:test+TestX+Test_Course',
|
||||
'income': '$25,000 - $40,000',
|
||||
'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.",
|
||||
'mktg-permission': False
|
||||
}
|
||||
response = self._submit_financial_assistance_form(
|
||||
form_data, submit_url='submit_financial_assistance_request_v2'
|
||||
)
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
|
||||
@ddt.data(
|
||||
({}, 400),
|
||||
({'username': 'wwhite'}, 403),
|
||||
({'username': 'dummy', 'course': 'bad course ID'}, 400)
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_submit_financial_assistance_errors(self, data, status):
|
||||
def test_submit_financial_assistance_errors(self, data, response_status):
|
||||
response = self._submit_financial_assistance_form(data)
|
||||
assert response.status_code == status
|
||||
assert response.status_code == response_status
|
||||
|
||||
def test_financial_assistance_login_required(self):
|
||||
for url in (
|
||||
|
||||
@@ -6,6 +6,7 @@ import hashlib
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse, HttpResponseBadRequest
|
||||
from edx_rest_api_client.client import OAuthAPIClient
|
||||
from oauth2_provider.models import Application
|
||||
from pytz import utc # lint-amnesty, pylint: disable=wrong-import-order
|
||||
@@ -180,20 +181,21 @@ def create_financial_assistance_application(form_data):
|
||||
"income": <income_from_range>,
|
||||
"learner_reasons": <TEST_LONG_STRING>,
|
||||
"learner_goals": <TEST_LONG_STRING>,
|
||||
"learner_plans": <TEST_LONG_STRING>
|
||||
"learner_plans": <TEST_LONG_STRING>,
|
||||
"allow_for_marketing": <Boolean>
|
||||
}
|
||||
TODO: marketing checkmark field will be added in the backend and needs to be updated here.
|
||||
"""
|
||||
response = _request_financial_assistance(
|
||||
'POST', f"{settings.CREATE_FINANCIAL_ASSISTANCE_APPLICATION_URL}/", data=form_data
|
||||
)
|
||||
if response.status_code == status.HTTP_200_OK:
|
||||
return True, None
|
||||
return HttpResponse(status=status.HTTP_204_NO_CONTENT)
|
||||
elif response.status_code == status.HTTP_400_BAD_REQUEST:
|
||||
return False, response.json().get('message')
|
||||
log.error(response.json().get('message'))
|
||||
return HttpResponseBadRequest(response.content)
|
||||
else:
|
||||
log.error('%s %s', UNEXPECTED_ERROR_CREATE_APPLICATION, response.content)
|
||||
return False, UNEXPECTED_ERROR_CREATE_APPLICATION
|
||||
return HttpResponse(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
def get_course_hash_value(course_key):
|
||||
|
||||
@@ -44,15 +44,9 @@ from rest_framework.decorators import api_view, throttle_classes
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.throttling import UserRateThrottle
|
||||
from web_fragments.fragment import Fragment
|
||||
from xmodule.course_module import (
|
||||
COURSE_VISIBILITY_PUBLIC,
|
||||
COURSE_VISIBILITY_PUBLIC_OUTLINE
|
||||
)
|
||||
from xmodule.course_module import COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import (
|
||||
ItemNotFoundError,
|
||||
NoPathToItem
|
||||
)
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
|
||||
from xmodule.tabs import CourseTabList
|
||||
from xmodule.x_module import STUDENT_VIEW
|
||||
|
||||
@@ -72,6 +66,7 @@ from lms.djangoapps.course_goals.models import UserActivity
|
||||
from lms.djangoapps.course_home_api.toggles import course_home_mfe_progress_tab_is_active
|
||||
from lms.djangoapps.courseware.access import has_access, has_ccx_coach_role
|
||||
from lms.djangoapps.courseware.access_utils import check_course_open_for_learner, check_public_access
|
||||
from lms.djangoapps.courseware.config import ENABLE_NEW_FINANCIAL_ASSISTANCE_FLOW
|
||||
from lms.djangoapps.courseware.courses import (
|
||||
can_self_enroll_in_course,
|
||||
course_open_for_self_enrollment,
|
||||
@@ -90,13 +85,10 @@ from lms.djangoapps.courseware.exceptions import CourseAccessRedirect, Redirect
|
||||
from lms.djangoapps.courseware.masquerade import is_masquerading_as_specific_student, setup_masquerade
|
||||
from lms.djangoapps.courseware.model_data import FieldDataCache
|
||||
from lms.djangoapps.courseware.models import BaseStudentModuleHistory, StudentModule
|
||||
from lms.djangoapps.courseware.permissions import (
|
||||
MASQUERADE_AS_STUDENT,
|
||||
VIEW_COURSE_HOME,
|
||||
VIEW_COURSEWARE,
|
||||
)
|
||||
from lms.djangoapps.courseware.permissions import MASQUERADE_AS_STUDENT, VIEW_COURSE_HOME, VIEW_COURSEWARE
|
||||
from lms.djangoapps.courseware.toggles import course_is_invitation_only
|
||||
from lms.djangoapps.courseware.user_state_client import DjangoXBlockUserStateClient
|
||||
from lms.djangoapps.courseware.utils import create_financial_assistance_application
|
||||
from lms.djangoapps.edxnotes.helpers import is_feature_enabled
|
||||
from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context
|
||||
from lms.djangoapps.grades.api import CourseGradeFactory
|
||||
@@ -111,7 +103,6 @@ from openedx.core.djangoapps.credit.api import (
|
||||
is_credit_course,
|
||||
is_user_eligible_for_credit
|
||||
)
|
||||
from openedx.core.lib.courses import get_course_by_id
|
||||
from openedx.core.djangoapps.enrollments.api import add_enrollment
|
||||
from openedx.core.djangoapps.enrollments.permissions import ENROLL_IN_COURSE
|
||||
from openedx.core.djangoapps.models.course_details import CourseDetails
|
||||
@@ -122,6 +113,7 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_
|
||||
from openedx.core.djangoapps.util.user_messages import PageLevelMessages
|
||||
from openedx.core.djangoapps.zendesk_proxy.utils import create_zendesk_ticket
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
from openedx.core.lib.courses import get_course_by_id
|
||||
from openedx.core.lib.mobile_utils import is_request_from_mobile_app
|
||||
from openedx.features.course_duration_limits.access import generate_course_expired_fragment
|
||||
from openedx.features.course_experience import DISABLE_UNIFIED_COURSE_TAB_FLAG, course_home_url
|
||||
@@ -1970,6 +1962,48 @@ def financial_assistance_request(request):
|
||||
return HttpResponse(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def financial_assistance_request_v2(request):
|
||||
"""
|
||||
Uses the new financial assistance application flow.
|
||||
Creates a post request to edx-financial-assistance backend.
|
||||
"""
|
||||
try:
|
||||
data = json.loads(request.body.decode('utf8'))
|
||||
username = data['username']
|
||||
# Simple sanity check that the session belongs to the user
|
||||
# submitting an FA request
|
||||
if request.user.username != username:
|
||||
return HttpResponseForbidden()
|
||||
|
||||
lms_user_id = request.user.id
|
||||
course_id = data['course']
|
||||
income = data['income']
|
||||
learner_reasons = data['reason_for_applying']
|
||||
learner_goals = data['goals']
|
||||
learner_plans = data['effort']
|
||||
allowed_for_marketing = data['mktg-permission']
|
||||
|
||||
except ValueError:
|
||||
# Thrown if JSON parsing fails
|
||||
return HttpResponseBadRequest('Could not parse request JSON.')
|
||||
except KeyError as err:
|
||||
# Thrown if fields are missing
|
||||
return HttpResponseBadRequest(f'The field {str(err)} is required.')
|
||||
|
||||
form_data = {
|
||||
'lms_user_id': lms_user_id,
|
||||
'course_id': course_id,
|
||||
'income': income,
|
||||
'learner_reasons': learner_reasons,
|
||||
'learner_goals': learner_goals,
|
||||
'learner_plans': learner_plans,
|
||||
'allowed_for_marketing': allowed_for_marketing
|
||||
}
|
||||
return create_financial_assistance_application(form_data)
|
||||
|
||||
|
||||
@login_required
|
||||
def financial_assistance_form(request):
|
||||
"""Render the financial assistance application form page."""
|
||||
@@ -1982,6 +2016,11 @@ def financial_assistance_form(request):
|
||||
annual_incomes = [
|
||||
{'name': _(income), 'value': income} for income in incomes # lint-amnesty, pylint: disable=translation-of-non-string
|
||||
]
|
||||
if ENABLE_NEW_FINANCIAL_ASSISTANCE_FLOW.is_enabled():
|
||||
submit_url = 'submit_financial_assistance_request_v2'
|
||||
else:
|
||||
submit_url = 'submit_financial_assistance_request'
|
||||
|
||||
return render_to_response('financial-assistance/apply.html', {
|
||||
'header_text': _get_fa_header(FINANCIAL_ASSISTANCE_HEADER),
|
||||
'student_faq_url': marketing_link('FAQ'),
|
||||
@@ -1994,7 +2033,7 @@ def financial_assistance_form(request):
|
||||
'name': user.profile.name,
|
||||
'country': str(user.profile.country.name),
|
||||
},
|
||||
'submit_url': reverse('submit_financial_assistance_request'),
|
||||
'submit_url': reverse(submit_url),
|
||||
'fields': [
|
||||
{
|
||||
'name': 'course',
|
||||
|
||||
@@ -943,6 +943,11 @@ if settings.FEATURES.get('ENABLE_FINANCIAL_ASSISTANCE_FORM'):
|
||||
'financial-assistance/submit/',
|
||||
courseware_views.financial_assistance_request,
|
||||
name='submit_financial_assistance_request'
|
||||
),
|
||||
path(
|
||||
'financial-assistance_v2/submit/',
|
||||
courseware_views.financial_assistance_request_v2,
|
||||
name='submit_financial_assistance_request_v2'
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user