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
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)
@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):
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
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:
"""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)
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)
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)
# 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.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')
......
# -*- 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):
......
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