Merge pull request #14739 from edx/ahsan/ECOM-7532-Add-eligible-for-financial-aid-bit-LMS-Sync-course-discovery
Add eligible for financial aid and sync with course discovery
This commit is contained in:
@@ -49,9 +49,10 @@ from courseware.user_state_client import DjangoXBlockUserStateClient
|
|||||||
from courseware.views.index import render_accordion
|
from courseware.views.index import render_accordion
|
||||||
from lms.djangoapps.commerce.utils import EcommerceService # pylint: disable=import-error
|
from lms.djangoapps.commerce.utils import EcommerceService # pylint: disable=import-error
|
||||||
from milestones.tests.utils import MilestonesTestCaseMixin
|
from milestones.tests.utils import MilestonesTestCaseMixin
|
||||||
|
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||||
|
from openedx.core.djangoapps.crawlers.models import CrawlersConfig
|
||||||
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
|
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
|
||||||
from openedx.core.lib.gating import api as gating_api
|
from openedx.core.lib.gating import api as gating_api
|
||||||
from openedx.core.djangoapps.crawlers.models import CrawlersConfig
|
|
||||||
from student.models import CourseEnrollment
|
from student.models import CourseEnrollment
|
||||||
from student.tests.factories import AdminFactory, UserFactory, CourseEnrollmentFactory
|
from student.tests.factories import AdminFactory, UserFactory, CourseEnrollmentFactory
|
||||||
from util.tests.mixins.enterprise import EnterpriseTestConsentRequired
|
from util.tests.mixins.enterprise import EnterpriseTestConsentRequired
|
||||||
@@ -808,44 +809,64 @@ class ViewsTestCase(ModuleStoreTestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertIn('Financial Assistance Application', response.content)
|
self.assertIn('Financial Assistance Application', response.content)
|
||||||
|
|
||||||
def test_financial_assistance_form(self):
|
@ddt.data(([CourseMode.AUDIT, CourseMode.VERIFIED], CourseMode.AUDIT, True, datetime.now(UTC) - timedelta(days=1)),
|
||||||
non_verified_course = CourseFactory.create().id
|
([CourseMode.AUDIT, CourseMode.VERIFIED], CourseMode.VERIFIED, True, None),
|
||||||
verified_course_verified_track = CourseFactory.create().id
|
([CourseMode.AUDIT, CourseMode.VERIFIED], CourseMode.AUDIT, False, None),
|
||||||
verified_course_audit_track = CourseFactory.create().id
|
([CourseMode.AUDIT], CourseMode.AUDIT, False, None))
|
||||||
verified_course_deadline_passed = CourseFactory.create().id
|
@ddt.unpack
|
||||||
unenrolled_course = CourseFactory.create().id
|
def test_financial_assistance_form_course_exclusion(
|
||||||
|
self, course_modes, enrollment_mode, eligible_for_aid, expiration):
|
||||||
|
"""Verify that learner cannot get the financial aid for the courses having one of the
|
||||||
|
following attributes:
|
||||||
|
1. User is enrolled in the verified mode.
|
||||||
|
2. Course is expired.
|
||||||
|
3. Course does not provide financial assistance.
|
||||||
|
4. Course does not have verified mode.
|
||||||
|
"""
|
||||||
|
# Create course
|
||||||
|
course = CourseFactory.create()
|
||||||
|
|
||||||
enrollments = (
|
# Create Course Modes
|
||||||
(non_verified_course, CourseMode.AUDIT, None),
|
for mode in course_modes:
|
||||||
(verified_course_verified_track, CourseMode.VERIFIED, None),
|
CourseModeFactory.create(mode_slug=mode, course_id=course.id, expiration_datetime=expiration)
|
||||||
(verified_course_audit_track, CourseMode.AUDIT, None),
|
|
||||||
(verified_course_deadline_passed, CourseMode.AUDIT, datetime.now(UTC) - timedelta(days=1))
|
# Enroll user in the course
|
||||||
)
|
CourseEnrollmentFactory(course_id=course.id, user=self.user, mode=enrollment_mode)
|
||||||
for course, mode, expiration in enrollments:
|
# load course into course overview
|
||||||
CourseModeFactory.create(mode_slug=CourseMode.AUDIT, course_id=course)
|
CourseOverview.get_from_id(course.id)
|
||||||
if course != non_verified_course:
|
|
||||||
CourseModeFactory.create(
|
# add whether course is eligible for financial aid or not
|
||||||
mode_slug=CourseMode.VERIFIED,
|
course_overview = CourseOverview.objects.get(id=course.id)
|
||||||
course_id=course,
|
course_overview.eligible_for_financial_aid = eligible_for_aid
|
||||||
expiration_datetime=expiration
|
course_overview.save()
|
||||||
)
|
|
||||||
CourseEnrollmentFactory(course_id=course, user=self.user, mode=mode)
|
|
||||||
|
|
||||||
url = reverse('financial_assistance_form')
|
url = reverse('financial_assistance_form')
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
# Ensure that the user can only apply for assistance in
|
self.assertNotIn(str(course.id), response.content)
|
||||||
# courses which have a verified mode which hasn't expired yet,
|
|
||||||
# where the user is not already enrolled in verified mode
|
def test_financial_assistance_form(self):
|
||||||
self.assertIn(str(verified_course_audit_track), response.content)
|
"""Verify that learner can get the financial aid for the course in which
|
||||||
for course in (
|
he/she is enrolled in audit mode whereas the course provide verified mode.
|
||||||
non_verified_course,
|
"""
|
||||||
verified_course_verified_track,
|
# Create course
|
||||||
verified_course_deadline_passed,
|
course = CourseFactory.create().id
|
||||||
unenrolled_course
|
|
||||||
):
|
# Create Course Modes
|
||||||
self.assertNotIn(str(course), response.content)
|
CourseModeFactory.create(mode_slug=CourseMode.AUDIT, course_id=course)
|
||||||
|
CourseModeFactory.create(mode_slug=CourseMode.VERIFIED, course_id=course)
|
||||||
|
|
||||||
|
# Enroll user in the course
|
||||||
|
CourseEnrollmentFactory(course_id=course, user=self.user, mode=CourseMode.AUDIT)
|
||||||
|
# load course into course overview
|
||||||
|
CourseOverview.get_from_id(course)
|
||||||
|
|
||||||
|
url = reverse('financial_assistance_form')
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
self.assertIn(str(course), response.content)
|
||||||
|
|
||||||
def _submit_financial_assistance_form(self, data):
|
def _submit_financial_assistance_form(self, data):
|
||||||
"""Submit a financial assistance request."""
|
"""Submit a financial assistance request."""
|
||||||
|
|||||||
@@ -1536,16 +1536,7 @@ def financial_assistance_request(request):
|
|||||||
def financial_assistance_form(request):
|
def financial_assistance_form(request):
|
||||||
"""Render the financial assistance application form page."""
|
"""Render the financial assistance application form page."""
|
||||||
user = request.user
|
user = request.user
|
||||||
enrolled_courses = [
|
enrolled_courses = get_financial_aid_courses(user)
|
||||||
{'name': enrollment.course_overview.display_name, 'value': unicode(enrollment.course_id)}
|
|
||||||
for enrollment in CourseEnrollment.enrollments_for_user(user).order_by('-created')
|
|
||||||
|
|
||||||
if enrollment.mode != CourseMode.VERIFIED and CourseMode.objects.filter(
|
|
||||||
Q(_expiration_datetime__isnull=True) | Q(_expiration_datetime__gt=datetime.now(UTC())),
|
|
||||||
course_id=enrollment.course_id,
|
|
||||||
mode_slug=CourseMode.VERIFIED
|
|
||||||
).exists()
|
|
||||||
]
|
|
||||||
incomes = ['Less than $5,000', '$5,000 - $10,000', '$10,000 - $15,000', '$15,000 - $20,000', '$20,000 - $25,000']
|
incomes = ['Less than $5,000', '$5,000 - $10,000', '$10,000 - $15,000', '$15,000 - $20,000', '$20,000 - $25,000']
|
||||||
annual_incomes = [
|
annual_incomes = [
|
||||||
{'name': _(income), 'value': income} for income in incomes # pylint: disable=translation-of-non-string
|
{'name': _(income), 'value': income} for income in incomes # pylint: disable=translation-of-non-string
|
||||||
@@ -1642,3 +1633,25 @@ def financial_assistance_form(request):
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def get_financial_aid_courses(user):
|
||||||
|
""" Retrieve the courses eligible for financial assistance. """
|
||||||
|
financial_aid_courses = []
|
||||||
|
for enrollment in CourseEnrollment.enrollments_for_user(user).order_by('-created'):
|
||||||
|
|
||||||
|
if enrollment.mode != CourseMode.VERIFIED and \
|
||||||
|
enrollment.course_overview.eligible_for_financial_aid and \
|
||||||
|
CourseMode.objects.filter(
|
||||||
|
Q(_expiration_datetime__isnull=True) | Q(_expiration_datetime__gt=datetime.now(UTC())),
|
||||||
|
course_id=enrollment.course_id,
|
||||||
|
mode_slug=CourseMode.VERIFIED).exists():
|
||||||
|
|
||||||
|
financial_aid_courses.append(
|
||||||
|
{
|
||||||
|
'name': enrollment.course_overview.display_name,
|
||||||
|
'value': unicode(enrollment.course_id)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return financial_aid_courses
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ class Command(BaseCommand):
|
|||||||
course_metadata_updated = 0
|
course_metadata_updated = 0
|
||||||
|
|
||||||
for course_run in course_runs:
|
for course_run in course_runs:
|
||||||
|
is_course_metadata_updated = False
|
||||||
marketing_url = course_run['marketing_url']
|
marketing_url = course_run['marketing_url']
|
||||||
|
eligible_for_financial_aid = course_run['eligible_for_financial_aid']
|
||||||
course_key = CourseKey.from_string(course_run['key'])
|
course_key = CourseKey.from_string(course_run['key'])
|
||||||
try:
|
try:
|
||||||
course_overview = CourseOverview.objects.get(id=course_key)
|
course_overview = CourseOverview.objects.get(id=course_key)
|
||||||
@@ -50,6 +52,14 @@ class Command(BaseCommand):
|
|||||||
# Check whether course overview's marketing url is outdated - this saves a db hit.
|
# Check whether course overview's marketing url is outdated - this saves a db hit.
|
||||||
if course_overview.marketing_url != marketing_url:
|
if course_overview.marketing_url != marketing_url:
|
||||||
course_overview.marketing_url = marketing_url
|
course_overview.marketing_url = marketing_url
|
||||||
|
is_course_metadata_updated = True
|
||||||
|
|
||||||
|
# Check whether course overview's eligible for financial aid is outdated
|
||||||
|
if course_overview.eligible_for_financial_aid != eligible_for_financial_aid:
|
||||||
|
course_overview.eligible_for_financial_aid = eligible_for_financial_aid
|
||||||
|
is_course_metadata_updated = True
|
||||||
|
|
||||||
|
if is_course_metadata_updated:
|
||||||
course_overview.save()
|
course_overview.save()
|
||||||
course_metadata_updated += 1
|
course_metadata_updated += 1
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ class TestSyncCourseRunsCommand(ModuleStoreTestCase):
|
|||||||
# create a catalog course run with the same course id.
|
# create a catalog course run with the same course id.
|
||||||
self.catalog_course_run = CourseRunFactory(
|
self.catalog_course_run = CourseRunFactory(
|
||||||
key=unicode(self.course.id),
|
key=unicode(self.course.id),
|
||||||
marketing_url='test_marketing_url'
|
marketing_url='test_marketing_url',
|
||||||
|
eligible_for_financial_aid=False
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_course_overview_marketing_url(self, course_id):
|
def get_course_overview_marketing_url(self, course_id):
|
||||||
@@ -38,18 +39,25 @@ class TestSyncCourseRunsCommand(ModuleStoreTestCase):
|
|||||||
"""
|
"""
|
||||||
return CourseOverview.objects.get(id=course_id).marketing_url
|
return CourseOverview.objects.get(id=course_id).marketing_url
|
||||||
|
|
||||||
def test_marketing_url_on_sync(self, mock_catalog_course_runs):
|
def test_course_run_sync(self, mock_catalog_course_runs):
|
||||||
"""
|
"""
|
||||||
Verify the updated marketing url on execution of the management command.
|
Verify on executing management command course overview data is updated
|
||||||
|
with course run data from course discovery.
|
||||||
"""
|
"""
|
||||||
mock_catalog_course_runs.return_value = [self.catalog_course_run]
|
mock_catalog_course_runs.return_value = [self.catalog_course_run]
|
||||||
earlier_marketing_url = self.get_course_overview_marketing_url(self.course.id)
|
earlier_marketing_url = self.get_course_overview_marketing_url(self.course.id)
|
||||||
|
course_overview = CourseOverview.objects.get(id=self.course.id)
|
||||||
|
earlier_eligible_for_financial_aid = course_overview.eligible_for_financial_aid
|
||||||
|
|
||||||
call_command('sync_course_runs')
|
call_command('sync_course_runs')
|
||||||
|
course_overview.refresh_from_db()
|
||||||
updated_marketing_url = self.get_course_overview_marketing_url(self.course.id)
|
updated_marketing_url = self.get_course_overview_marketing_url(self.course.id)
|
||||||
|
updated_eligible_for_financial_aid = course_overview.eligible_for_financial_aid
|
||||||
# Assert that the Marketing URL has changed.
|
# Assert that the Marketing URL has changed.
|
||||||
self.assertNotEqual(earlier_marketing_url, updated_marketing_url)
|
self.assertNotEqual(earlier_marketing_url, updated_marketing_url)
|
||||||
|
self.assertNotEqual(earlier_eligible_for_financial_aid, updated_eligible_for_financial_aid)
|
||||||
self.assertEqual(updated_marketing_url, 'test_marketing_url')
|
self.assertEqual(updated_marketing_url, 'test_marketing_url')
|
||||||
|
self.assertEqual(updated_eligible_for_financial_aid, False)
|
||||||
|
|
||||||
@mock.patch(COMMAND_MODULE + '.log.info')
|
@mock.patch(COMMAND_MODULE + '.log.info')
|
||||||
def test_course_overview_does_not_exist(self, mock_log_info, mock_catalog_course_runs):
|
def test_course_overview_does_not_exist(self, mock_log_info, mock_catalog_course_runs):
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ class CourseRunFactory(DictFactoryBase):
|
|||||||
image = ImageFactory()
|
image = ImageFactory()
|
||||||
key = factory.LazyFunction(generate_course_run_key)
|
key = factory.LazyFunction(generate_course_run_key)
|
||||||
marketing_url = factory.Faker('url')
|
marketing_url = factory.Faker('url')
|
||||||
|
eligible_for_financial_aid = True
|
||||||
seats = factory.LazyFunction(partial(generate_instances, SeatFactory))
|
seats = factory.LazyFunction(partial(generate_instances, SeatFactory))
|
||||||
pacing_type = 'self_paced'
|
pacing_type = 'self_paced'
|
||||||
short_description = factory.Faker('sentence')
|
short_description = factory.Faker('sentence')
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('course_overviews', '0011_courseoverview_marketing_url'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='courseoverview',
|
||||||
|
name='eligible_for_financial_aid',
|
||||||
|
field=models.BooleanField(default=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -98,6 +98,7 @@ class CourseOverview(TimeStampedModel):
|
|||||||
effort = TextField(null=True)
|
effort = TextField(null=True)
|
||||||
self_paced = BooleanField(default=False)
|
self_paced = BooleanField(default=False)
|
||||||
marketing_url = TextField(null=True)
|
marketing_url = TextField(null=True)
|
||||||
|
eligible_for_financial_aid = BooleanField(default=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _create_from_course(cls, course):
|
def _create_from_course(cls, course):
|
||||||
|
|||||||
Reference in New Issue
Block a user