From 33bb739e50535d271d9ec68783e267d424ae339a Mon Sep 17 00:00:00 2001 From: AliAkbar Date: Fri, 15 Apr 2022 16:25:41 +0500 Subject: [PATCH] feat: use new flow with with financial assistance form --- lms/djangoapps/courseware/tests/test_utils.py | 42 +++++------ lms/djangoapps/courseware/tests/test_views.py | 44 +++++++----- lms/djangoapps/courseware/utils.py | 12 ++-- lms/djangoapps/courseware/views/views.py | 69 +++++++++++++++---- lms/urls.py | 5 ++ 5 files changed, 115 insertions(+), 57 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_utils.py b/lms/djangoapps/courseware/tests/test_utils.py index 69d818c99f..81841f99ad 100644 --- a/lms/djangoapps/courseware/tests/test_utils.py +++ b/lms/djangoapps/courseware/tests/test_utils.py @@ -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 diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 0f7ff5d3fd..fa85055046 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -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 ( diff --git a/lms/djangoapps/courseware/utils.py b/lms/djangoapps/courseware/utils.py index 87afad8cbb..9eafc2a775 100644 --- a/lms/djangoapps/courseware/utils.py +++ b/lms/djangoapps/courseware/utils.py @@ -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": , "learner_reasons": , "learner_goals": , - "learner_plans": + "learner_plans": , + "allow_for_marketing": } - 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): diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 128a6a6b28..bf5ada7dda 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -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', diff --git a/lms/urls.py b/lms/urls.py index a985dec640..21fb97fba4 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -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' ) ]