Commit 9e9c99ac by Ahsan Ulhaq

Add eligible for financial aid and sync with course discovery

ECOM-7532
parent 82dea974
...@@ -49,9 +49,10 @@ from courseware.user_state_client import DjangoXBlockUserStateClient ...@@ -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): ...@@ -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)
@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()
# 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)
self.assertNotIn(str(course.id), response.content)
def test_financial_assistance_form(self): def test_financial_assistance_form(self):
non_verified_course = CourseFactory.create().id """Verify that learner can get the financial aid for the course in which
verified_course_verified_track = CourseFactory.create().id he/she is enrolled in audit mode whereas the course provide verified mode.
verified_course_audit_track = CourseFactory.create().id """
verified_course_deadline_passed = CourseFactory.create().id # Create course
unenrolled_course = CourseFactory.create().id course = CourseFactory.create().id
enrollments = ( # Create Course Modes
(non_verified_course, CourseMode.AUDIT, None), CourseModeFactory.create(mode_slug=CourseMode.AUDIT, course_id=course)
(verified_course_verified_track, CourseMode.VERIFIED, None), CourseModeFactory.create(mode_slug=CourseMode.VERIFIED, course_id=course)
(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, user=self.user, mode=CourseMode.AUDIT)
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)
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)
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.assertIn(str(course), response.content)
# 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)
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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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')
......
# -*- 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): ...@@ -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):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment