Skip to content
Snippets Groups Projects
Commit 8d3fa543 authored by Diana Huang's avatar Diana Huang
Browse files

Add in-place enrollment to course home page.

parent 4f5b033c
No related merge requests found
......@@ -22,6 +22,7 @@ from courseware.module_render import get_module
from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import Http404, QueryDict
from enrollment.api import get_course_enrollment_details
from edxmako.shortcuts import render_to_string
from fs.errors import ResourceNotFoundError
from lms.djangoapps.courseware.courseware_access_exception import CoursewareAccessException
......@@ -173,6 +174,30 @@ def can_self_enroll_in_course(course_key):
return True
def course_open_for_self_enrollment(course_key):
"""
For a given course_key, determine if the course is available for enrollment
"""
# Check to see if learners can enroll themselves.
if not can_self_enroll_in_course(course_key):
return False
# Check the enrollment start and end dates.
course_details = get_course_enrollment_details(unicode(course_key))
now = datetime.now().replace(tzinfo=pytz.UTC)
start = course_details['enrollment_start']
end = course_details['enrollment_end']
start = start if start is not None else now
end = end if end is not None else now
# If we are not within the start and end date for enrollment.
if now < start or end < now:
return False
return True
def find_file(filesystem, dirs, filename):
"""
Looks for a filename in a list of dirs on a filesystem, in the specified order.
......
......@@ -4,8 +4,10 @@ Tests for course access
"""
import itertools
import datetime
import ddt
import mock
import pytz
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test.client import RequestFactory
......@@ -13,6 +15,7 @@ from django.test.utils import override_settings
from nose.plugins.attrib import attr
from courseware.courses import (
course_open_for_self_enrollment,
get_cms_block_link,
get_cms_course_link,
get_course_about_section,
......@@ -322,6 +325,52 @@ class CoursesRenderTest(ModuleStoreTestCase):
self.assertIn("this module is temporarily unavailable", course_about)
class CourseEnrollmentOpenTests(ModuleStoreTestCase):
def setUp(self):
super(CourseEnrollmentOpenTests, self).setUp()
self.now = datetime.datetime.now().replace(tzinfo=pytz.UTC)
def test_course_enrollment_open(self):
start = self.now - datetime.timedelta(days=1)
end = self.now + datetime.timedelta(days=1)
course = CourseFactory(enrollment_start=start, enrollment_end=end)
self.assertTrue(course_open_for_self_enrollment(course.id))
def test_course_enrollment_closed_future(self):
start = self.now + datetime.timedelta(days=1)
end = self.now + datetime.timedelta(days=2)
course = CourseFactory(enrollment_start=start, enrollment_end=end)
self.assertFalse(course_open_for_self_enrollment(course.id))
def test_course_enrollment_closed_past(self):
start = self.now - datetime.timedelta(days=2)
end = self.now - datetime.timedelta(days=1)
course = CourseFactory(enrollment_start=start, enrollment_end=end)
self.assertFalse(course_open_for_self_enrollment(course.id))
def test_course_enrollment_dates_missing(self):
course = CourseFactory()
self.assertTrue(course_open_for_self_enrollment(course.id))
def test_course_enrollment_dates_missing_start(self):
end = self.now + datetime.timedelta(days=1)
course = CourseFactory(enrollment_end=end)
self.assertTrue(course_open_for_self_enrollment(course.id))
end = self.now - datetime.timedelta(days=1)
course = CourseFactory(enrollment_end=end)
self.assertFalse(course_open_for_self_enrollment(course.id))
def test_course_enrollment_dates_missing_end(self):
start = self.now - datetime.timedelta(days=1)
course = CourseFactory(enrollment_start=start)
self.assertTrue(course_open_for_self_enrollment(course.id))
start = self.now + datetime.timedelta(days=1)
course = CourseFactory(enrollment_start=start)
self.assertFalse(course_open_for_self_enrollment(course.id))
@attr(shard=1)
@ddt.ddt
class CourseInstantiationTests(ModuleStoreTestCase):
......
......@@ -19,6 +19,7 @@ from courseware.access import has_access, has_ccx_coach_role
from courseware.access_utils import check_course_open_for_learner
from courseware.courses import (
can_self_enroll_in_course,
course_open_for_self_enrollment,
get_course,
get_course_overview_with_access,
get_course_with_access,
......@@ -312,6 +313,7 @@ def course_info(request, course_id):
'request': request,
'masquerade_user': user,
'course_id': course_key.to_deprecated_string(),
'url_to_enroll': CourseTabView.url_to_enroll(course_key),
'cache': None,
'course': course,
'staff_access': staff_access,
......@@ -321,7 +323,6 @@ def course_info(request, course_id):
'show_enroll_banner': show_enroll_banner,
'user_is_enrolled': user_is_enrolled,
'dates_fragment': dates_fragment,
'url_to_enroll': CourseTabView.url_to_enroll(course_key),
'course_tools': course_tools,
}
context.update(
......@@ -449,15 +450,22 @@ class CourseTabView(EdxFragmentView):
)
)
elif not is_enrolled and not is_staff:
PageLevelMessages.register_warning_message(
request,
Text(_('You must be enrolled in the course to see course content. {enroll_link}.')).format(
enroll_link=HTML('<a href="{url_to_enroll}">{enroll_link_label}</a>').format(
url_to_enroll=CourseTabView.url_to_enroll(course_key),
enroll_link_label=_("Enroll now"),
# Only show enroll button if course is open for enrollment.
if course_open_for_self_enrollment(course_key):
enroll_message = _('You must be enrolled in the course to see course content. \
{enroll_link_start}Enroll now{enroll_link_end}.')
PageLevelMessages.register_warning_message(
request,
Text(enroll_message).format(
enroll_link_start=HTML('<button class="enroll-btn btn-link">'),
enroll_link_end=HTML('</button>')
)
)
)
else:
PageLevelMessages.register_warning_message(
request,
Text(_('You must be enrolled in the course to see course content.'))
)
@staticmethod
def handle_exceptions(request, course, exception):
......
<button class="enroll-btn btn-link">Enroll Now</button>
/*
* Course Enrollment on the Course Home page
*/
export class CourseEnrollment { // eslint-disable-line import/prefer-default-export
/**
* Redirect to a URL. Mainly useful for mocking out in tests.
* @param {string} url The URL to redirect to.
*/
static redirect(url) {
window.location.href = url;
}
static refresh() {
window.location.reload(false);
}
static createEnrollment(courseId) {
const data = JSON.stringify({
course_details: { course_id: courseId },
});
const enrollmentAPI = '/api/enrollment/v1/enrollment';
const trackSelection = '/course_modes/choose/';
return () =>
$.ajax(
{
type: 'POST',
url: enrollmentAPI,
data,
contentType: 'application/json',
}).done(() => {
window.analytics.track('edx.bi.user.course-home.enrollment');
CourseEnrollment.refresh();
}).fail(() => {
// If the simple enrollment we attempted failed, go to the track selection page,
// which is better for handling more complex enrollment situations.
CourseEnrollment.redirect(trackSelection + courseId);
});
}
constructor(buttonClass, courseId) {
$(buttonClass).click(CourseEnrollment.createEnrollment(courseId));
}
}
/* globals $, loadFixtures */
import {
expectRequest,
requests as mockRequests,
respondWithJson,
respondWithError,
} from 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers';
import { CourseEnrollment } from '../Enrollment';
describe('CourseEnrollment tests', () => {
describe('Ensure button behavior', () => {
const endpointUrl = '/api/enrollment/v1/enrollment';
const courseId = 'course-v1:edX+DemoX+Demo_Course';
const enrollButtonClass = '.enroll-btn';
window.analytics = jasmine.createSpyObj('analytics', ['page', 'track', 'trackLink']);
beforeEach(() => {
loadFixtures('course_experience/fixtures/enrollment-button.html');
new CourseEnrollment('.enroll-btn', courseId); // eslint-disable-line no-new
});
it('Verify that we reload on success', () => {
const requests = mockRequests(this);
$(enrollButtonClass).click();
expectRequest(
requests,
'POST',
endpointUrl,
`{"course_details":{"course_id":"${courseId}"}}`,
);
spyOn(CourseEnrollment, 'refresh');
respondWithJson(requests);
expect(CourseEnrollment.refresh).toHaveBeenCalled();
expect(window.analytics.track).toHaveBeenCalled();
requests.restore();
});
it('Verify that we redirect to track selection on fail', () => {
const requests = mockRequests(this);
$(enrollButtonClass).click();
spyOn(CourseEnrollment, 'redirect');
respondWithError(requests, 403);
expect(CourseEnrollment.redirect).toHaveBeenCalled();
requests.restore();
});
});
});
......@@ -112,3 +112,7 @@ from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, SHOW_REV
courseToolLink: ".course-tool-link",
});
</%static:webpack>
<%static:webpack entry="Enrollment">
new CourseEnrollment('.enroll-btn', '${course_key | n, js_escaped_string}');
</%static:webpack>
......@@ -23,6 +23,7 @@ var wpconfig = {
CourseSock: './openedx/features/course_experience/static/course_experience/js/CourseSock.js',
CourseTalkReviews: './openedx/features/course_experience/static/course_experience/js/CourseTalkReviews.js',
WelcomeMessage: './openedx/features/course_experience/static/course_experience/js/WelcomeMessage.js',
Enrollment: './openedx/features/course_experience/static/course_experience/js/Enrollment.js',
Import: './cms/static/js/features/import/factories/import.js'
},
......
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