From fc468bf696a4233f742406fe0a45ce4905b6c9fd Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Wed, 22 Oct 2014 16:38:45 -0400 Subject: [PATCH 1/2] Set up basic enrollment infrastructure. --- .../course_modes/tests/test_views.py | 1 + common/djangoapps/course_modes/views.py | 6 +- common/djangoapps/enrollment/data.py | 2 +- common/djangoapps/enrollment/views.py | 4 +- lms/envs/common.py | 1 + .../enrollment_interface_spec.js | 15 ++++ .../js/spec/student_account/login_spec.js | 4 + .../student_account/enrollment_interface.js | 78 +++++++++++++++++++ .../js/student_account/models/LoginModel.js | 27 +++++-- .../student_account/models/RegisterModel.js | 26 +++++-- .../student_account/login_and_register.html | 1 + 11 files changed, 147 insertions(+), 18 deletions(-) create mode 100644 lms/static/js/spec/student_account/enrollment_interface_spec.js create mode 100644 lms/static/js/student_account/enrollment_interface.js diff --git a/common/djangoapps/course_modes/tests/test_views.py b/common/djangoapps/course_modes/tests/test_views.py index 30b242671b..ec0e16dc63 100644 --- a/common/djangoapps/course_modes/tests/test_views.py +++ b/common/djangoapps/course_modes/tests/test_views.py @@ -31,6 +31,7 @@ class CourseModeViewTest(ModuleStoreTestCase): self.user = UserFactory.create(username="Bob", email="bob@example.com", password="edx") self.client.login(username=self.user.username, password="edx") + @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') @ddt.data( # is_active?, enrollment_mode, upgrade?, redirect? (True, 'verified', True, False), # User has an active verified enrollment and is trying to upgrade diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index b70e9e3028..eb0e5c4816 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -26,10 +26,10 @@ class ChooseModeView(View): When a get request is used, shows the selection page. - When a post request is used, assumes that it is a form submission + When a post request is used, assumes that it is a form submission from the selection page, parses the response, and then sends user to the next step in the flow. - + """ @method_decorator(login_required) @@ -50,7 +50,7 @@ class ChooseModeView(View): """ course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(request.user, course_key) - + upgrade = request.GET.get('upgrade', False) request.session['attempting_upgrade'] = upgrade diff --git a/common/djangoapps/enrollment/data.py b/common/djangoapps/enrollment/data.py index f72c5fcb01..82db6734fa 100644 --- a/common/djangoapps/enrollment/data.py +++ b/common/djangoapps/enrollment/data.py @@ -73,7 +73,7 @@ def update_course_enrollment(student_id, course_id, mode=None, is_active=None): course_key = CourseKey.from_string(course_id) student = User.objects.get(username=student_id) if not CourseEnrollment.is_enrolled(student, course_key): - enrollment = CourseEnrollment.enroll(student, course_key) + enrollment = CourseEnrollment.enroll(student, course_key, check_access=True) else: enrollment = CourseEnrollment.objects.get(user=student, course_id=course_key) diff --git a/common/djangoapps/enrollment/views.py b/common/djangoapps/enrollment/views.py index 9546403c27..eea296ce4f 100644 --- a/common/djangoapps/enrollment/views.py +++ b/common/djangoapps/enrollment/views.py @@ -10,7 +10,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.throttling import UserRateThrottle from enrollment import api -from student.models import NonExistentCourseError +from student.models import NonExistentCourseError, CourseEnrollmentException class EnrollmentUserThrottle(UserRateThrottle): @@ -74,3 +74,5 @@ def get_course_enrollment(request, course_id=None): return Response(status=status.HTTP_400_BAD_REQUEST) except api.EnrollmentNotFoundError: return Response(status=status.HTTP_400_BAD_REQUEST) + except CourseEnrollmentException: + return Response(status=status.HTTP_400_BAD_REQUEST) diff --git a/lms/envs/common.py b/lms/envs/common.py index 588abf375b..b62672c1fd 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1028,6 +1028,7 @@ instructor_dash_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/ins student_account_js = [ 'js/common_helpers/rwd_header_footer.js', 'js/common_helpers/edx.utils.validate.js', + 'js/student_account/enrollment_interface.js', 'js/student_account/models/LoginModel.js', 'js/student_account/models/RegisterModel.js', 'js/student_account/models/PasswordResetModel.js', diff --git a/lms/static/js/spec/student_account/enrollment_interface_spec.js b/lms/static/js/spec/student_account/enrollment_interface_spec.js new file mode 100644 index 0000000000..eb9113f282 --- /dev/null +++ b/lms/static/js/spec/student_account/enrollment_interface_spec.js @@ -0,0 +1,15 @@ +define(['js/common_helpers/template_helpers', 'js/student_account/enrollment_interface'], + function(TemplateHelpers) { + describe("edx.student.account.EnrollmentInterface", function() { + 'use strict'; + it("find course modes using modeInArray ", function() { + var course_modes = [ + { + }, + { + } + ]; + }); + }); + } +); diff --git a/lms/static/js/spec/student_account/login_spec.js b/lms/static/js/spec/student_account/login_spec.js index 190972acb9..9f8ff48f2b 100644 --- a/lms/static/js/spec/student_account/login_spec.js +++ b/lms/static/js/spec/student_account/login_spec.js @@ -39,6 +39,10 @@ define(['js/common_helpers/template_helpers', 'js/student_account/views/LoginVie it("allows the user to navigate to the password assistance form", function() { // TODO }); + + it("enrolls the student into the right location and forwards them properly", function() { + // TODO + }); }); } ); diff --git a/lms/static/js/student_account/enrollment_interface.js b/lms/static/js/student_account/enrollment_interface.js new file mode 100644 index 0000000000..09aa02360d --- /dev/null +++ b/lms/static/js/student_account/enrollment_interface.js @@ -0,0 +1,78 @@ +var edx = edx || {}; + +(function($, _, gettext) { + 'use strict'; + + edx.student = edx.student || {}; + edx.student.account = edx.student.account || {}; + + edx.student.account.EnrollmentInterface = { + courseUrl: '/enrollment/v0/course/', + studentUrl: '/enrollment/v0/student', + trackSelectionUrl: '/course_modes/choose/', + headers: { + 'X-CSRFToken': $.cookie('csrftoken') + }, + + studentInformation: function(course_key) { + // retrieve student enrollment information + }, + + courseInformation: function(course_key) { + // retrieve course information from the enrollment API + }, + + modeInArray: function(mode_slug, course_modes) { + // finds whether or not a particular course mode slug exists + // in an array of course modes + var result = _.find(course_modes, function(mode){ return mode.slug === mode_slug; }); + return result != undefined; + }, + + enroll: function(course_key, forward_url){ + var me = this; + // attempt to enroll a student in a course + $.ajax({ + url: this.courseUrl + course_key, + type: 'POST', + data: {}, + headers: this.headers + }).done(function(data){ + me.postEnrollmentHandler(course_key, data, forward_url); + } + ).fail(function(data, textStatus) { + me.enrollmentFailureHandler(course_key, data, forward_url); + }); + }, + + enrollmentFailureHandler: function(course_key, data, forward_url) { + // handle failures to enroll via the API + if(data.status == 400) { + // This status code probably means we don't have permissions to register for this course. + // look at the contents of the response + var course = $.parseJSON(data.responseText); + // see if it's a professional ed course + if('course_modes' in course && this.modeInArray('professional', course.course_modes)) { + // forward appropriately + forward_url = this.trackSelectionUrl + course_key; + } + } + // TODO: if we have a paid registration mode, add item to the cart and send them along + + // TODO: we should figure out how to handle errors here eventually + window.location.href = forward_url; + }, + + postEnrollmentHandler: function(course_key, data, forward_url) { + // Determine whether or not the course needs to be redirected to + // a particular page. + var course = data.course, + course_modes = course.course_modes; + + // send the user to the track selection page, because it will do the right thing + forward_url = this.trackSelectionUrl + course_key; + + window.location.href = forward_url; + } + }; +})(jQuery, _, gettext); diff --git a/lms/static/js/student_account/models/LoginModel.js b/lms/static/js/student_account/models/LoginModel.js index 22f46db41a..f852d01c71 100644 --- a/lms/static/js/student_account/models/LoginModel.js +++ b/lms/static/js/student_account/models/LoginModel.js @@ -6,6 +6,7 @@ var edx = edx || {}; edx.student = edx.student || {}; edx.student.account = edx.student.account || {}; + edx.student.account.LoginModel = Backbone.Model.extend({ defaults: { @@ -32,17 +33,31 @@ var edx = edx || {}; headers: headers }) .done(function() { - var query = window.location.search, - url = '/dashboard'; + var enrollment = edx.student.account.EnrollmentInterface, + query = new URI(window.location.search), + url = '/dashboard', + query_map = query.search(true), + next = ''; + + // check for forwarding url + if("next" in query_map) { + next = query_map['next']; + if(!window.isExternal(next)){ + url = next; + } + } model.trigger('sync'); - // If query string in url go back to that page - if ( query.length > 1 ) { - url = query.substring( query.indexOf('=') + 1 ); + // if we need to enroll in the course, mark as enrolled + if('enrollment_action' in query_map && query_map['enrollment_action'] === 'enroll'){ + enrollment.enroll(query_map['course_id'], url); + } + else { + window.location.href = url; } - window.location.href = url; + }) .fail( function( error ) { model.trigger('error', error); diff --git a/lms/static/js/student_account/models/RegisterModel.js b/lms/static/js/student_account/models/RegisterModel.js index b5f93774b7..3e36e9b727 100644 --- a/lms/static/js/student_account/models/RegisterModel.js +++ b/lms/static/js/student_account/models/RegisterModel.js @@ -39,17 +39,29 @@ var edx = edx || {}; headers: headers }) .done(function() { - var query = window.location.search, - url = '/dashboard'; + var enrollment = edx.student.account.EnrollmentInterface, + query = new URI(window.location.search), + url = '/dashboard', + query_map = query.search(true), + next = ''; + + // check for forwarding url + if("next" in query_map) { + next = query_map['next']; + if(!window.isExternal(next)){ + url = next; + } + } model.trigger('sync'); - // If query string in url go back to that page - if ( query.length > 1 ) { - url = query.substring( query.indexOf('=') + 1 ); + // if we need to enroll in the course, mark as enrolled + if('enrollment_action' in query_map && query_map['enrollment_action'] === 'enroll'){ + enrollment.enroll(query_map['course_id'], url); + } + else { + window.location.href = url; } - - window.location.href = url; }) .fail( function( error ) { model.trigger('error', error); diff --git a/lms/templates/student_account/login_and_register.html b/lms/templates/student_account/login_and_register.html index 6cc9dcf12b..debe7910fe 100644 --- a/lms/templates/student_account/login_and_register.html +++ b/lms/templates/student_account/login_and_register.html @@ -8,6 +8,7 @@ <%block name="js_extra"> + <%static:js group='student_account'/> From 47979e5bcfbd960784831ca5ce7855badff0194c Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 30 Oct 2014 14:00:44 -0400 Subject: [PATCH 2/2] Add tests for the enrollment_interface --- lms/static/js/spec/main.js | 8 ++++++-- lms/static/js/spec/student_account/access_spec.js | 2 +- .../spec/student_account/enrollment_interface_spec.js | 11 +++++++++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/lms/static/js/spec/main.js b/lms/static/js/spec/main.js index 20e41a02a5..b246460f92 100644 --- a/lms/static/js/spec/main.js +++ b/lms/static/js/spec/main.js @@ -260,7 +260,10 @@ exports: 'NotificationView', deps: ['backbone', 'jquery', 'underscore'] }, - + 'js/student_account/enrollment_interface': { + exports: 'js/student_account/enrollment_interface', + deps: ['jquery', 'underscore', 'gettext'] + }, // Student account registration/login // Loaded explicitly until these are converted to RequireJS 'js/student_account/views/FormView': { @@ -310,7 +313,7 @@ 'js/student_account/views/RegisterView', 'underscore.string' ] - }, + } }, }); @@ -327,6 +330,7 @@ 'lms/include/js/spec/student_account/login_spec.js', 'lms/include/js/spec/student_account/register_spec.js', 'lms/include/js/spec/student_account/password_reset_spec.js', + 'lms/include/js/spec/student_account/enrollment_interface_spec.js', 'lms/include/js/spec/student_profile/profile.js', ]); diff --git a/lms/static/js/spec/student_account/access_spec.js b/lms/static/js/spec/student_account/access_spec.js index ecba86b2fe..9915060819 100644 --- a/lms/static/js/spec/student_account/access_spec.js +++ b/lms/static/js/spec/student_account/access_spec.js @@ -1,5 +1,5 @@ define(['js/common_helpers/template_helpers', 'js/student_account/views/AccessView'], - function(TemplateHelpers) { + function(TemplateHelpers, AccessView) { describe('edx.student.account.AccessView', function() { 'use strict'; diff --git a/lms/static/js/spec/student_account/enrollment_interface_spec.js b/lms/static/js/spec/student_account/enrollment_interface_spec.js index eb9113f282..eb0d98d022 100644 --- a/lms/static/js/spec/student_account/enrollment_interface_spec.js +++ b/lms/static/js/spec/student_account/enrollment_interface_spec.js @@ -1,14 +1,21 @@ define(['js/common_helpers/template_helpers', 'js/student_account/enrollment_interface'], - function(TemplateHelpers) { + function(TemplateHelpers, EnrollmentInterface) { describe("edx.student.account.EnrollmentInterface", function() { 'use strict'; + it("find course modes using modeInArray ", function() { var course_modes = [ { + slug: 'honor' }, { + slug: 'professional' } - ]; + ], + + expect(EnrollmentInterface.modeInArray('professional')).toBe(true); + expect(EnrollmentInterface.modeInArray('audit')).toBe(false); + }); }); }