Merge pull request #30243 from openedx/aakbar/PROD-2739
feat: use new financial assistance flow in FinancialAssistanceTool
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
Constants for courseware app.
|
||||
"""
|
||||
UNEXPECTED_ERROR_IS_ELIGIBLE = "An unexpected error occurred while fetching " \
|
||||
"financial assistance eligibility criteria for a course"
|
||||
"financial assistance eligibility criteria for this course"
|
||||
UNEXPECTED_ERROR_APPLICATION_STATUS = "An unexpected error occurred while getting " \
|
||||
"financial assistance application status"
|
||||
UNEXPECTED_ERROR_CREATE_APPLICATION = "An unexpected error occurred while creating financial assistance application"
|
||||
|
||||
@@ -7,12 +7,14 @@ import datetime
|
||||
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext as _
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from openedx.features.course_experience.course_tools import CourseTool
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from lms.djangoapps.courseware.utils import _use_new_financial_assistance_flow
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.features.course_experience.course_tools import CourseTool
|
||||
|
||||
|
||||
class FinancialAssistanceTool(CourseTool):
|
||||
@@ -86,4 +88,6 @@ class FinancialAssistanceTool(CourseTool):
|
||||
"""
|
||||
Returns the URL for this tool for the specified course key.
|
||||
"""
|
||||
if _use_new_financial_assistance_flow(str(course_key)):
|
||||
return reverse('financial_assistance_v2', args=[course_key])
|
||||
return reverse('financial_assistance')
|
||||
|
||||
@@ -843,6 +843,17 @@ class ViewsTestCase(BaseViewsTestCase):
|
||||
assert response.status_code == 200
|
||||
self.assertContains(response, 'Financial Assistance Application')
|
||||
|
||||
@patch('lms.djangoapps.courseware.views.views._use_new_financial_assistance_flow', return_value=True)
|
||||
@patch('lms.djangoapps.courseware.views.views.is_eligible_for_financial_aid', return_value=(False, 'error reason'))
|
||||
def test_new_financial_assistance_page_course_ineligible(self, *args):
|
||||
"""
|
||||
Test to verify the financial_assistance view against an ineligible course returns an error page.
|
||||
"""
|
||||
url = reverse('financial_assistance_v2', args=['course-v1:test+TestX+Test_Course'])
|
||||
response = self.client.get(url)
|
||||
assert response.status_code == 200
|
||||
self.assertContains(response, 'This course is not eligible for Financial Assistance for the following reason:')
|
||||
|
||||
@ddt.data(([CourseMode.AUDIT, CourseMode.VERIFIED], CourseMode.AUDIT, True, YESTERDAY),
|
||||
([CourseMode.AUDIT, CourseMode.VERIFIED], CourseMode.VERIFIED, True, None),
|
||||
([CourseMode.AUDIT, CourseMode.VERIFIED], CourseMode.AUDIT, False, None),
|
||||
@@ -902,10 +913,11 @@ class ViewsTestCase(BaseViewsTestCase):
|
||||
|
||||
self.assertContains(response, str(course))
|
||||
|
||||
def _submit_financial_assistance_form(self, data, submit_url='submit_financial_assistance_request'):
|
||||
def _submit_financial_assistance_form(self, data, submit_url='submit_financial_assistance_request',
|
||||
referrer_url=None):
|
||||
"""Submit a financial assistance request."""
|
||||
url = reverse(submit_url)
|
||||
return self.client.post(url, json.dumps(data), content_type='application/json')
|
||||
return self.client.post(url, json.dumps(data), content_type='application/json', HTTP_REFERER=referrer_url)
|
||||
|
||||
@patch.object(views, 'create_zendesk_ticket', return_value=200)
|
||||
def test_submit_financial_assistance_request(self, mock_create_zendesk_ticket):
|
||||
@@ -968,7 +980,12 @@ class ViewsTestCase(BaseViewsTestCase):
|
||||
@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):
|
||||
@ddt.data(
|
||||
('/financial-assistance/course-v1:test+TestX+Test_Course/apply/', status.HTTP_204_NO_CONTENT),
|
||||
('/financial-assistance/course-v1:invalid+ErrorX+Invalid_Course/apply/', status.HTTP_400_BAD_REQUEST)
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_submit_financial_assistance_request_v2(self, referrer_url, expected_status, *args):
|
||||
form_data = {
|
||||
'username': self.user.username,
|
||||
'course': 'course-v1:test+TestX+Test_Course',
|
||||
@@ -979,9 +996,11 @@ class ViewsTestCase(BaseViewsTestCase):
|
||||
'mktg-permission': False
|
||||
}
|
||||
response = self._submit_financial_assistance_form(
|
||||
form_data, submit_url='submit_financial_assistance_request_v2'
|
||||
form_data,
|
||||
submit_url='submit_financial_assistance_request_v2',
|
||||
referrer_url=referrer_url
|
||||
)
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
assert response.status_code == expected_status
|
||||
|
||||
@ddt.data(
|
||||
({}, 400),
|
||||
|
||||
@@ -17,12 +17,14 @@ from xmodule.partitions.partitions_service import PartitionService # lint-amnes
|
||||
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from lms.djangoapps.commerce.utils import EcommerceService
|
||||
from lms.djangoapps.courseware.config import ENABLE_NEW_FINANCIAL_ASSISTANCE_FLOW
|
||||
from lms.djangoapps.courseware.constants import (
|
||||
UNEXPECTED_ERROR_APPLICATION_STATUS,
|
||||
UNEXPECTED_ERROR_CREATE_APPLICATION,
|
||||
UNEXPECTED_ERROR_IS_ELIGIBLE
|
||||
)
|
||||
from lms.djangoapps.courseware.models import FinancialAssistanceConfiguration
|
||||
from openedx.core.djangoapps.waffle_utils.models import WaffleFlagCourseOverrideModel
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -210,3 +212,19 @@ def get_course_hash_value(course_key):
|
||||
return int(m.hexdigest(), base=16) % 100
|
||||
|
||||
return out_of_bound_value
|
||||
|
||||
|
||||
def _use_new_financial_assistance_flow(course_id):
|
||||
"""
|
||||
Returns if the course_id can be used in the new financial assistance flow.
|
||||
"""
|
||||
is_financial_assistance_enabled_for_course = WaffleFlagCourseOverrideModel.override_value(
|
||||
ENABLE_NEW_FINANCIAL_ASSISTANCE_FLOW.name, course_id
|
||||
)
|
||||
financial_assistance_configuration = FinancialAssistanceConfiguration.current()
|
||||
if financial_assistance_configuration.enabled and (
|
||||
is_financial_assistance_enabled_for_course == WaffleFlagCourseOverrideModel.ALL_CHOICES.on or
|
||||
get_course_hash_value(course_id) <= financial_assistance_configuration.fa_backend_enabled_courses_percentage
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -68,7 +68,6 @@ 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,7 +89,11 @@ from lms.djangoapps.courseware.models import BaseStudentModuleHistory, StudentMo
|
||||
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.courseware.utils import (
|
||||
_use_new_financial_assistance_flow,
|
||||
create_financial_assistance_application,
|
||||
is_eligible_for_financial_aid
|
||||
)
|
||||
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
|
||||
@@ -1902,10 +1905,18 @@ FA_SHORT_ANSWER_INSTRUCTIONS = _('Use between 1250 and 2500 characters or so in
|
||||
|
||||
|
||||
@login_required
|
||||
def financial_assistance(_request):
|
||||
def financial_assistance(request, course_id=None):
|
||||
"""Render the initial financial assistance page."""
|
||||
reason = None
|
||||
apply_url = reverse('financial_assistance_form')
|
||||
if course_id and _use_new_financial_assistance_flow(course_id):
|
||||
_, reason = is_eligible_for_financial_aid(course_id)
|
||||
apply_url = reverse('financial_assistance_form_v2', args=[course_id])
|
||||
|
||||
return render_to_response('financial-assistance/financial-assistance.html', {
|
||||
'header_text': _get_fa_header(FINANCIAL_ASSISTANCE_HEADER)
|
||||
'header_text': _get_fa_header(FINANCIAL_ASSISTANCE_HEADER),
|
||||
'apply_url': apply_url,
|
||||
'reason': reason
|
||||
})
|
||||
|
||||
|
||||
@@ -1992,8 +2003,10 @@ def financial_assistance_request_v2(request):
|
||||
if request.user.username != username:
|
||||
return HttpResponseForbidden()
|
||||
|
||||
lms_user_id = request.user.id
|
||||
course_id = data['course']
|
||||
if course_id and course_id not in request.META.get('HTTP_REFERER'):
|
||||
return HttpResponseBadRequest('Invalid Course ID provided.')
|
||||
lms_user_id = request.user.id
|
||||
income = data['income']
|
||||
learner_reasons = data['reason_for_applying']
|
||||
learner_goals = data['goals']
|
||||
@@ -2020,10 +2033,13 @@ def financial_assistance_request_v2(request):
|
||||
|
||||
|
||||
@login_required
|
||||
def financial_assistance_form(request):
|
||||
def financial_assistance_form(request, course_id=None):
|
||||
"""Render the financial assistance application form page."""
|
||||
user = request.user
|
||||
enrolled_courses = get_financial_aid_courses(user)
|
||||
disabled = False
|
||||
if course_id:
|
||||
disabled = True
|
||||
enrolled_courses = get_financial_aid_courses(user, course_id)
|
||||
incomes = ['Less than $5,000', '$5,000 - $10,000', '$10,000 - $15,000', '$15,000 - $20,000', '$20,000 - $25,000',
|
||||
'$25,000 - $40,000', '$40,000 - $55,000', '$55,000 - $70,000', '$70,000 - $85,000',
|
||||
'$85,000 - $100,000', 'More than $100,000']
|
||||
@@ -2031,7 +2047,7 @@ 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():
|
||||
if course_id and _use_new_financial_assistance_flow(course_id):
|
||||
submit_url = 'submit_financial_assistance_request_v2'
|
||||
else:
|
||||
submit_url = 'submit_financial_assistance_request'
|
||||
@@ -2057,6 +2073,7 @@ def financial_assistance_form(request):
|
||||
'placeholder': '',
|
||||
'defaultValue': '',
|
||||
'required': True,
|
||||
'disabled': disabled,
|
||||
'options': enrolled_courses,
|
||||
'instructions': gettext(
|
||||
'Select the course for which you want to earn a verified certificate. If'
|
||||
@@ -2130,8 +2147,9 @@ def financial_assistance_form(request):
|
||||
})
|
||||
|
||||
|
||||
def get_financial_aid_courses(user):
|
||||
def get_financial_aid_courses(user, course_id=None):
|
||||
""" Retrieve the courses eligible for financial assistance. """
|
||||
use_new_flow = False
|
||||
financial_aid_courses = []
|
||||
for enrollment in CourseEnrollment.enrollments_for_user(user).order_by('-created'):
|
||||
|
||||
@@ -2142,6 +2160,15 @@ def get_financial_aid_courses(user):
|
||||
Q(_expiration_datetime__isnull=True) | Q(_expiration_datetime__gt=datetime.now(UTC)),
|
||||
course_id=enrollment.course_id,
|
||||
mode_slug=CourseMode.VERIFIED).exists():
|
||||
# This is a workaround to set course_id before disabling the field in case of new financial assistance flow.
|
||||
if str(enrollment.course_overview) == course_id:
|
||||
financial_aid_courses = [{
|
||||
'name': enrollment.course_overview.display_name,
|
||||
'value': str(enrollment.course_id),
|
||||
'default': True
|
||||
}]
|
||||
use_new_flow = True
|
||||
break
|
||||
|
||||
financial_aid_courses.append(
|
||||
{
|
||||
@@ -2150,6 +2177,9 @@ def get_financial_aid_courses(user):
|
||||
}
|
||||
)
|
||||
|
||||
if course_id is not None and use_new_flow is False:
|
||||
# We don't want to show financial_aid_courses if the course_id is not found in the enrolled courses.
|
||||
return []
|
||||
return financial_aid_courses
|
||||
|
||||
|
||||
|
||||
@@ -5,47 +5,54 @@ from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from common.djangoapps.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>
|
||||
% if reason:
|
||||
<p>This course is not eligible for Financial Assistance for the following reason: <b>${reason}</b></p>
|
||||
% else:
|
||||
<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>
|
||||
## Translators: This string will not be used in Open edX installations.
|
||||
<p>${_("Dear edX Learner,")}</p>
|
||||
|
||||
<div class="financial-assistance financial-assistance-body">
|
||||
<h2>${_("A Note to Learners")}</h2>
|
||||
## Translators: This string will not be used in Open edX installations.
|
||||
<p>${_("Dear edX Learner,")}</p>
|
||||
## Translators: This string will not be used in Open edX installations.
|
||||
<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>
|
||||
|
||||
## Translators: This string will not be used in Open edX installations.
|
||||
<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>
|
||||
## Translators: This string will not be used in Open edX installations.
|
||||
<p>${_("If you are interested in working toward a Verified Certificate, but cannot afford to pay the fee, please apply now. Please note that financial assistance is limited and may not be awarded to all eligible candidates.")}</p>
|
||||
|
||||
## Translators: This string will not be used in Open edX installations.
|
||||
<p>${_("If you are interested in working toward a Verified Certificate, but cannot afford to pay the fee, please apply now. Please note that financial assistance is limited and may not be awarded to all eligible candidates.")}</p>
|
||||
## Translators: This string will not be used in Open edX installations.
|
||||
<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>
|
||||
|
||||
## Translators: This string will not be used in Open edX installations.
|
||||
<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>
|
||||
## Translators: This string will not be used in Open edX installations.
|
||||
<p>${_("If your application is approved, we'll give you instructions for verifying your identity on edx.org so you can start working toward completing your edX course.")}</p>
|
||||
|
||||
## Translators: This string will not be used in Open edX installations.
|
||||
<p>${_("If your application is approved, we'll give you instructions for verifying your identity on edx.org so you can start working toward completing your edX course.")}</p>
|
||||
## Translators: This string will not be used in Open edX installations.
|
||||
<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>
|
||||
## Translators: This string will not be used in Open edX installations. Do not translate the name "Anant".
|
||||
<p class="signature">${_("Sincerely, Anant")}</p>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
## Translators: This string will not be used in Open edX installations.
|
||||
<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>
|
||||
## Translators: This string will not be used in Open edX installations. Do not translate the name "Anant".
|
||||
<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>
|
||||
<%
|
||||
faq_link = marketing_link('FAQ')
|
||||
%>
|
||||
% if faq_link != '#':
|
||||
<a class="faq-link" href="${faq_link}">${_("Back to Student FAQs")}</a>
|
||||
% endif
|
||||
% if not reason:
|
||||
<a class="action-link" href="${apply_url}">${_("Apply for Financial Assistance")}</a>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
<% });
|
||||
} %>
|
||||
<% if ( required ) { %> aria-required="true" required<% } %>
|
||||
<% if ( typeof disabled !== 'undefined' && disabled ) { %> disabled<% } %>
|
||||
|
||||
>
|
||||
<% _.each(options, function(el) { %>
|
||||
<option value="<%- el.value%>"<% if ( el.default ) { %> data-isdefault="true" selected<% } %>><%- el.name %></option>
|
||||
|
||||
28
lms/urls.py
28
lms/urls.py
@@ -4,18 +4,18 @@ URLs for LMS
|
||||
|
||||
from config_models.views import ConfigurationModelCurrentAPIView
|
||||
from django.conf import settings
|
||||
from django.urls import include, re_path
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin import autodiscover as django_autodiscover
|
||||
from django.urls import path
|
||||
from django.urls import include, path, re_path
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic.base import RedirectView
|
||||
from edx_api_doc_tools import make_docs_urls
|
||||
from edx_django_utils.plugins import get_plugin_url_patterns
|
||||
from django.contrib import admin
|
||||
|
||||
from common.djangoapps.student import views as student_views
|
||||
from common.djangoapps.util import views as util_views
|
||||
from lms.djangoapps.branding import views as branding_views
|
||||
from lms.djangoapps.debug import views as debug_views
|
||||
from lms.djangoapps.courseware.masquerade import MasqueradeView
|
||||
from lms.djangoapps.courseware.module_render import (
|
||||
handle_xblock_callback,
|
||||
@@ -26,13 +26,14 @@ from lms.djangoapps.courseware.module_render import (
|
||||
from lms.djangoapps.courseware.views import views as courseware_views
|
||||
from lms.djangoapps.courseware.views.index import CoursewareIndex
|
||||
from lms.djangoapps.courseware.views.views import CourseTabView, EnrollStaffView, StaticCourseTabView
|
||||
from lms.djangoapps.debug import views as debug_views
|
||||
from lms.djangoapps.discussion import views as discussion_views
|
||||
from lms.djangoapps.discussion.config.settings import is_forum_daily_digest_enabled
|
||||
from lms.djangoapps.discussion.notification_prefs import views as notification_prefs_views
|
||||
from lms.djangoapps.instructor.views import instructor_dashboard as instructor_dashboard_views
|
||||
from lms.djangoapps.instructor_task import views as instructor_task_views
|
||||
from lms.djangoapps.staticbook import views as staticbook_views
|
||||
from lms.djangoapps.static_template_view import views as static_template_view_views
|
||||
from lms.djangoapps.staticbook import views as staticbook_views
|
||||
from openedx.core.apidocs import api_info
|
||||
from openedx.core.djangoapps.auth_exchange.views import LoginWithAccessTokenView
|
||||
from openedx.core.djangoapps.catalog.models import CatalogIntegration
|
||||
@@ -51,8 +52,6 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_
|
||||
from openedx.core.djangoapps.user_authn.views.login import redirect_to_lms_login
|
||||
from openedx.core.djangoapps.verified_track_content import views as verified_track_content_views
|
||||
from openedx.features.enterprise_support.api import enterprise_enabled
|
||||
from common.djangoapps.student import views as student_views
|
||||
from common.djangoapps.util import views as util_views
|
||||
|
||||
RESET_COURSE_DEADLINES_NAME = 'reset_course_deadlines'
|
||||
RENDER_XBLOCK_NAME = 'render_xblock'
|
||||
@@ -240,9 +239,10 @@ urlpatterns += [
|
||||
# Multicourse wiki (Note: wiki urls must be above the courseware ones because of
|
||||
# the custom tab catch-all)
|
||||
if settings.WIKI_ENABLED:
|
||||
from wiki.urls import get_pattern as wiki_pattern
|
||||
from lms.djangoapps.course_wiki import views as course_wiki_views
|
||||
from django_notify.urls import get_pattern as notify_pattern
|
||||
from wiki.urls import get_pattern as wiki_pattern
|
||||
|
||||
from lms.djangoapps.course_wiki import views as course_wiki_views
|
||||
|
||||
wiki_url_patterns, wiki_app_name = wiki_pattern()
|
||||
notify_url_patterns, notify_app_name = notify_pattern()
|
||||
@@ -948,6 +948,16 @@ if settings.FEATURES.get('ENABLE_FINANCIAL_ASSISTANCE_FORM'):
|
||||
'financial-assistance_v2/submit/',
|
||||
courseware_views.financial_assistance_request_v2,
|
||||
name='submit_financial_assistance_request_v2'
|
||||
),
|
||||
re_path(
|
||||
fr'financial-assistance/{settings.COURSE_ID_PATTERN}/apply/',
|
||||
courseware_views.financial_assistance_form,
|
||||
name='financial_assistance_form_v2'
|
||||
),
|
||||
re_path(
|
||||
fr'financial-assistance/{settings.COURSE_ID_PATTERN}',
|
||||
courseware_views.financial_assistance,
|
||||
name='financial_assistance_v2'
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user