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 lms.djangoapps.commerce.utils import EcommerceService # pylint: disable=import-error
|
||||
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.lib.gating import api as gating_api
|
||||
from openedx.core.djangoapps.crawlers.models import CrawlersConfig
|
||||
from student.models import CourseEnrollment
|
||||
from student.tests.factories import AdminFactory, UserFactory, CourseEnrollmentFactory
|
||||
from util.tests.mixins.enterprise import EnterpriseTestConsentRequired
|
||||
@@ -808,44 +809,64 @@ class ViewsTestCase(ModuleStoreTestCase):
|
||||
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
|
||||
@ddt.data(([CourseMode.AUDIT, CourseMode.VERIFIED], CourseMode.AUDIT, True, datetime.now(UTC) - timedelta(days=1)),
|
||||
([CourseMode.AUDIT, CourseMode.VERIFIED], CourseMode.VERIFIED, True, None),
|
||||
([CourseMode.AUDIT, CourseMode.VERIFIED], CourseMode.AUDIT, False, None),
|
||||
([CourseMode.AUDIT], CourseMode.AUDIT, False, None))
|
||||
@ddt.unpack
|
||||
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 = (
|
||||
(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.create(mode_slug=CourseMode.AUDIT, course_id=course)
|
||||
if course != non_verified_course:
|
||||
CourseModeFactory.create(
|
||||
mode_slug=CourseMode.VERIFIED,
|
||||
course_id=course,
|
||||
expiration_datetime=expiration
|
||||
)
|
||||
CourseEnrollmentFactory(course_id=course, user=self.user, mode=mode)
|
||||
# Create Course Modes
|
||||
for mode in course_modes:
|
||||
CourseModeFactory.create(mode_slug=mode, course_id=course.id, expiration_datetime=expiration)
|
||||
|
||||
# Enroll user in the course
|
||||
CourseEnrollmentFactory(course_id=course.id, user=self.user, mode=enrollment_mode)
|
||||
# load course into course overview
|
||||
CourseOverview.get_from_id(course.id)
|
||||
|
||||
# add whether course is eligible for financial aid or not
|
||||
course_overview = CourseOverview.objects.get(id=course.id)
|
||||
course_overview.eligible_for_financial_aid = eligible_for_aid
|
||||
course_overview.save()
|
||||
|
||||
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)
|
||||
self.assertNotIn(str(course.id), response.content)
|
||||
|
||||
def test_financial_assistance_form(self):
|
||||
"""Verify that learner can get the financial aid for the course in which
|
||||
he/she is enrolled in audit mode whereas the course provide verified mode.
|
||||
"""
|
||||
# Create course
|
||||
course = CourseFactory.create().id
|
||||
|
||||
# Create Course Modes
|
||||
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):
|
||||
"""Submit a financial assistance request."""
|
||||
|
||||
@@ -1536,16 +1536,7 @@ def financial_assistance_request(request):
|
||||
def financial_assistance_form(request):
|
||||
"""Render the financial assistance application form page."""
|
||||
user = request.user
|
||||
enrolled_courses = [
|
||||
{'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()
|
||||
]
|
||||
enrolled_courses = get_financial_aid_courses(user)
|
||||
incomes = ['Less than $5,000', '$5,000 - $10,000', '$10,000 - $15,000', '$15,000 - $20,000', '$20,000 - $25,000']
|
||||
annual_incomes = [
|
||||
{'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
|
||||
|
||||
for course_run in course_runs:
|
||||
is_course_metadata_updated = False
|
||||
marketing_url = course_run['marketing_url']
|
||||
eligible_for_financial_aid = course_run['eligible_for_financial_aid']
|
||||
course_key = CourseKey.from_string(course_run['key'])
|
||||
try:
|
||||
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.
|
||||
if 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_metadata_updated += 1
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ class TestSyncCourseRunsCommand(ModuleStoreTestCase):
|
||||
# create a catalog course run with the same course id.
|
||||
self.catalog_course_run = CourseRunFactory(
|
||||
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):
|
||||
@@ -38,18 +39,25 @@ class TestSyncCourseRunsCommand(ModuleStoreTestCase):
|
||||
"""
|
||||
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]
|
||||
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')
|
||||
course_overview.refresh_from_db()
|
||||
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.
|
||||
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_eligible_for_financial_aid, False)
|
||||
|
||||
@mock.patch(COMMAND_MODULE + '.log.info')
|
||||
def test_course_overview_does_not_exist(self, mock_log_info, mock_catalog_course_runs):
|
||||
|
||||
@@ -88,6 +88,7 @@ class CourseRunFactory(DictFactoryBase):
|
||||
image = ImageFactory()
|
||||
key = factory.LazyFunction(generate_course_run_key)
|
||||
marketing_url = factory.Faker('url')
|
||||
eligible_for_financial_aid = True
|
||||
seats = factory.LazyFunction(partial(generate_instances, SeatFactory))
|
||||
pacing_type = 'self_paced'
|
||||
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)
|
||||
self_paced = BooleanField(default=False)
|
||||
marketing_url = TextField(null=True)
|
||||
eligible_for_financial_aid = BooleanField(default=True)
|
||||
|
||||
@classmethod
|
||||
def _create_from_course(cls, course):
|
||||
|
||||
Reference in New Issue
Block a user