Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
8d672bc8
Commit
8d672bc8
authored
Jun 13, 2016
by
Simon Chen
Committed by
GitHub
Jun 13, 2016
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
ECOM-3206 allow run selection and enrollment (#12685)
parent
781a2049
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
265 additions
and
61 deletions
+265
-61
lms/djangoapps/learner_dashboard/tests/test_programs.py
+2
-1
lms/djangoapps/learner_dashboard/tests/test_utils.py
+23
-0
lms/djangoapps/learner_dashboard/utils.py
+16
-0
lms/djangoapps/learner_dashboard/views.py
+12
-1
lms/static/js/learner_dashboard/models/course_card_model.js
+39
-17
lms/static/js/learner_dashboard/models/course_enroll_model.js
+19
-0
lms/static/js/learner_dashboard/views/collection_list_view.js
+1
-0
lms/static/js/learner_dashboard/views/course_card_view.js
+12
-3
lms/static/js/learner_dashboard/views/course_enroll_view.js
+72
-6
lms/static/js/learner_dashboard/views/program_details_view.js
+1
-1
lms/static/js/spec/learner_dashboard/course_card_view_spec.js
+21
-15
lms/static/js/spec/learner_dashboard/course_enroll_view_spec.js
+0
-0
lms/static/js/spec/learner_dashboard/program_details_header_spec.js
+4
-3
lms/static/sass/elements/_course-card.scss
+17
-8
lms/templates/learner_dashboard/course_card.underscore
+3
-4
lms/templates/learner_dashboard/course_enroll.underscore
+21
-0
lms/templates/learner_dashboard/program_details.html
+1
-1
lms/templates/learner_dashboard/program_header_view.underscore
+1
-1
No files found.
lms/djangoapps/learner_dashboard/tests/test_programs.py
View file @
8d672bc8
...
...
@@ -284,7 +284,8 @@ class TestProgramDetails(ProgramsApiConfigMixin, SharedModuleStoreTestCase):
def
_assert_program_data_present
(
self
,
response
):
"""Verify that program data is present."""
self
.
assertContains
(
response
,
'programData'
)
self
.
assertContains
(
response
,
'programListingUrl'
)
self
.
assertContains
(
response
,
'urls'
)
self
.
assertContains
(
response
,
'program_listing_url'
)
self
.
assertContains
(
response
,
self
.
data
[
'name'
])
self
.
_assert_programs_tab_present
(
response
)
...
...
lms/djangoapps/learner_dashboard/tests/test_utils.py
0 → 100644
View file @
8d672bc8
"""
Unit test module covering utils module
"""
import
ddt
from
django.test
import
TestCase
from
lms.djangoapps.learner_dashboard
import
utils
@ddt.ddt
class
TestUtils
(
TestCase
):
"""
The test case class covering the all the utils functions
"""
@ddt.data
(
'path1/'
,
'/path1/path2/'
,
'/'
,
''
)
def
test_strip_course_id
(
self
,
path
):
"""
Test to make sure the function 'strip_course_id'
handles various url input
"""
actual
=
utils
.
strip_course_id
(
path
+
unicode
(
utils
.
FAKE_COURSE_KEY
))
self
.
assertEqual
(
actual
,
path
)
lms/djangoapps/learner_dashboard/utils.py
0 → 100644
View file @
8d672bc8
"""
The utility methods and functions to help the djangoapp logic
"""
from
opaque_keys.edx.keys
import
CourseKey
FAKE_COURSE_KEY
=
CourseKey
.
from_string
(
'course-v1:fake+course+run'
)
def
strip_course_id
(
path
):
"""
The utility function to help remove the fake
course ID from the url path
"""
course_id
=
unicode
(
FAKE_COURSE_KEY
)
return
path
.
split
(
course_id
)[
0
]
lms/djangoapps/learner_dashboard/views.py
View file @
8d672bc8
...
...
@@ -11,6 +11,10 @@ from edxmako.shortcuts import render_to_response
from
openedx.core.djangoapps.credentials.utils
import
get_programs_credentials
from
openedx.core.djangoapps.programs.models
import
ProgramsApiConfig
from
openedx.core.djangoapps.programs
import
utils
from
lms.djangoapps.learner_dashboard.utils
import
(
FAKE_COURSE_KEY
,
strip_course_id
)
@login_required
...
...
@@ -63,9 +67,16 @@ def program_details(request, program_id):
program_data
=
utils
.
supplement_program_data
(
program_data
,
request
.
user
)
show_program_listing
=
ProgramsApiConfig
.
current
()
.
show_program_listing
urls
=
{
'program_listing_url'
:
reverse
(
'program_listing_view'
),
'track_selection_url'
:
strip_course_id
(
reverse
(
'course_modes_choose'
,
kwargs
=
{
'course_id'
:
FAKE_COURSE_KEY
})),
'commerce_api_url'
:
reverse
(
'commerce_api:v0:baskets:create'
)
}
context
=
{
'program_data'
:
program_data
,
'
program_listing_url'
:
reverse
(
'program_listing_view'
)
,
'
urls'
:
urls
,
'show_program_listing'
:
show_program_listing
,
'nav_hidden'
:
True
,
'disable_courseware_js'
:
True
,
...
...
lms/static/js/learner_dashboard/models/course_card_model.js
View file @
8d672bc8
...
...
@@ -11,27 +11,49 @@
initialize
:
function
(
data
)
{
if
(
data
){
this
.
context
=
data
;
//we should populate our model by looking at the run_modes
if
(
data
.
run_modes
.
length
>
0
){
//We only have 1 run mode for this program
this
.
setActiveRunMode
(
data
.
run_modes
[
0
]);
}
this
.
setActiveRunMode
(
this
.
getRunMode
(
data
.
run_modes
));
}
},
getRunMode
:
function
(
runModes
){
//we should populate our model by looking at the run_modes
if
(
runModes
.
length
>
0
){
if
(
runModes
.
length
===
1
){
return
runModes
[
0
];
}
else
{
//We need to implement logic here to select the
//most relevant run mode for the student to enroll
return
runModes
[
0
];
}
}
else
{
return
null
;
}
},
setActiveRunMode
:
function
(
runMode
){
this
.
set
({
display_name
:
this
.
context
.
display_name
,
key
:
this
.
context
.
key
,
marketing_url
:
runMode
.
marketing_url
||
''
,
start_date
:
runMode
.
start_date
,
end_date
:
runMode
.
end_date
,
is_enrolled
:
runMode
.
is_enrolled
,
is_enrollment_open
:
runMode
.
is_enrollment_open
,
course_url
:
runMode
.
course_url
||
''
,
course_image_url
:
runMode
.
course_image_url
||
''
,
mode_slug
:
runMode
.
mode_slug
});
if
(
runMode
){
this
.
set
({
display_name
:
this
.
context
.
display_name
,
key
:
this
.
context
.
key
,
marketing_url
:
runMode
.
marketing_url
||
''
,
start_date
:
runMode
.
start_date
,
end_date
:
runMode
.
end_date
,
is_enrolled
:
runMode
.
is_enrolled
,
is_enrollment_open
:
runMode
.
is_enrollment_open
,
course_key
:
runMode
.
course_key
,
course_url
:
runMode
.
course_url
||
''
,
course_image_url
:
runMode
.
course_image_url
||
''
,
mode_slug
:
runMode
.
mode_slug
,
run_key
:
runMode
.
run_key
});
}
},
updateRun
:
function
(
runKey
){
var
selectedRun
=
_
.
findWhere
(
this
.
get
(
'run_modes'
),
{
run_key
:
runKey
});
if
(
selectedRun
){
this
.
setActiveRunMode
(
selectedRun
);
}
}
});
});
...
...
lms/static/js/learner_dashboard/models/course_enroll_model.js
0 → 100644
View file @
8d672bc8
/**
* Store data to enroll learners into the course
*/
;(
function
(
define
)
{
'use strict'
;
define
([
'backbone'
],
function
(
Backbone
)
{
return
Backbone
.
Model
.
extend
({
defaults
:
{
course_id
:
''
,
optIn
:
false
,
}
});
}
);
}).
call
(
this
,
define
||
RequireJS
.
define
);
lms/static/js/learner_dashboard/views/collection_list_view.js
View file @
8d672bc8
...
...
@@ -46,6 +46,7 @@
if
(
this
.
titleContext
){
this
.
$el
.
before
(
HtmlUtils
.
ensureHtml
(
this
.
getTitleHtml
()).
toString
());
}
this
.
$el
.
html
(
childList
);
}
},
...
...
lms/static/js/learner_dashboard/views/course_card_view.js
View file @
8d672bc8
...
...
@@ -6,6 +6,7 @@
'underscore'
,
'gettext'
,
'edx-ui-toolkit/js/utils/html-utils'
,
'js/learner_dashboard/models/course_enroll_model'
,
'js/learner_dashboard/views/course_enroll_view'
,
'text!../../../templates/learner_dashboard/course_card.underscore'
],
...
...
@@ -15,6 +16,7 @@
_
,
gettext
,
HtmlUtils
,
EnrollModel
,
CourseEnrollView
,
pageTpl
)
{
...
...
@@ -23,8 +25,14 @@
tpl
:
HtmlUtils
.
template
(
pageTpl
),
initialize
:
function
()
{
initialize
:
function
(
options
)
{
this
.
enrollModel
=
new
EnrollModel
();
if
(
options
.
context
&&
options
.
context
.
urls
){
this
.
urlModel
=
new
Backbone
.
Model
(
options
.
context
.
urls
);
this
.
enrollModel
.
urlRoot
=
this
.
urlModel
.
get
(
'commerce_api_url'
);
}
this
.
render
();
this
.
listenTo
(
this
.
model
,
'change'
,
this
.
render
);
},
render
:
function
()
{
...
...
@@ -35,9 +43,10 @@
postRender
:
function
(){
this
.
enrollView
=
new
CourseEnrollView
({
$
e
l
:
this
.
$
(
'.course-actions'
),
$
parentE
l
:
this
.
$
(
'.course-actions'
),
model
:
this
.
model
,
context
:
this
.
context
urlModel
:
this
.
urlModel
,
enrollModel
:
this
.
enrollModel
});
}
});
...
...
lms/static/js/learner_dashboard/views/course_enroll_view.js
View file @
8d672bc8
...
...
@@ -20,23 +20,89 @@
tpl
:
HtmlUtils
.
template
(
pageTpl
),
events
:
{
'click .enroll-button'
:
'handleEnroll'
'click .enroll-button'
:
'handleEnroll'
,
'change .run-select'
:
'handleRunSelect'
,
},
initialize
:
function
(
options
)
{
if
(
options
.
$el
){
this
.
$el
=
options
.
$el
;
this
.
render
();
this
.
$parentEl
=
options
.
$parentEl
;
this
.
enrollModel
=
options
.
enrollModel
;
this
.
urlModel
=
options
.
urlModel
;
this
.
render
();
if
(
this
.
urlModel
){
this
.
trackSelectionUrl
=
this
.
urlModel
.
get
(
'track_selection_url'
);
}
},
render
:
function
()
{
var
filledTemplate
=
this
.
tpl
(
this
.
model
.
toJSON
());
HtmlUtils
.
setHtml
(
this
.
$el
,
filledTemplate
);
var
filledTemplate
;
if
(
this
.
$parentEl
&&
this
.
enrollModel
&&
this
.
model
.
get
(
'course_key'
)){
filledTemplate
=
this
.
tpl
(
this
.
model
.
toJSON
());
HtmlUtils
.
setHtml
(
this
.
$el
,
filledTemplate
);
HtmlUtils
.
setHtml
(
this
.
$parentEl
,
HtmlUtils
.
HTML
(
this
.
$el
));
}
},
handleEnroll
:
function
(){
//Enrollment click event handled here
if
(
!
this
.
model
.
get
(
'is_enrolled'
)){
// actually enroll
this
.
enrollModel
.
save
({
course_id
:
this
.
model
.
get
(
'course_key'
)
},
{
success
:
_
.
bind
(
this
.
enrollSuccess
,
this
),
error
:
_
.
bind
(
this
.
enrollError
,
this
)
});
}
},
handleRunSelect
:
function
(
event
){
var
runKey
;
if
(
event
.
target
){
runKey
=
$
(
event
.
target
).
val
();
if
(
runKey
){
this
.
model
.
updateRun
(
runKey
);
}
}
},
enrollSuccess
:
function
(){
var
courseKey
=
this
.
model
.
get
(
'course_key'
);
if
(
this
.
trackSelectionUrl
)
{
// Go to track selection page
this
.
redirect
(
this
.
trackSelectionUrl
+
courseKey
);
}
else
{
this
.
model
.
set
({
is_enrolled
:
true
});
}
},
enrollError
:
function
(
model
,
response
)
{
if
(
response
.
status
===
403
&&
response
.
responseJSON
.
user_message_url
)
{
/**
* Check if we've been blocked from the course
* because of country access rules.
* If so, redirect to a page explaining to the user
* why they were blocked.
*/
this
.
redirect
(
response
.
responseJSON
.
user_message_url
);
}
else
if
(
this
.
trackSelectionUrl
){
/**
* Otherwise, go to the track selection page as usual.
* This can occur, for example, when a course does not
* have a free enrollment mode, so we can't auto-enroll.
*/
this
.
redirect
(
this
.
trackSelectionUrl
+
this
.
model
.
get
(
'course_key'
)
);
}
},
redirect
:
function
(
url
)
{
window
.
location
.
href
=
url
;
}
});
}
...
...
lms/static/js/learner_dashboard/views/program_details_view.js
View file @
8d672bc8
...
...
@@ -51,7 +51,7 @@
el
:
'.js-course-list'
,
childView
:
CourseCardView
,
collection
:
this
.
courseCardCollection
,
context
:
this
.
programModel
.
toJSON
()
,
context
:
this
.
options
,
titleContext
:
{
el
:
'h2'
,
title
:
'Course List'
...
...
lms/static/js/spec/learner_dashboard/course_card_view_spec.js
View file @
8d672bc8
...
...
@@ -10,7 +10,6 @@ define([
describe
(
'Course Card View'
,
function
()
{
var
view
=
null
,
courseCardModel
,
setupView
,
context
=
{
course_modes
:
[],
display_name
:
'Astrophysics: Exploring Exoplanets'
,
...
...
@@ -32,7 +31,7 @@ define([
is_enrolled
:
true
,
certificate_url
:
''
,
}]
}
;
}
,
setupView
=
function
(
isEnrolled
){
context
.
run_modes
[
0
].
is_enrolled
=
isEnrolled
;
...
...
@@ -41,6 +40,17 @@ define([
view
=
new
CourseCardView
({
model
:
courseCardModel
});
},
validateCourseInfoDisplay
=
function
(){
//DRY validation for course card in enrolled state
expect
(
view
.
$
(
'.header-img'
).
attr
(
'src'
)).
toEqual
(
context
.
run_modes
[
0
].
course_image_url
);
expect
(
view
.
$
(
'.course-details .course-title-link'
).
text
().
trim
()).
toEqual
(
context
.
display_name
);
expect
(
view
.
$
(
'.course-details .course-title-link'
).
attr
(
'href'
)).
toEqual
(
context
.
run_modes
[
0
].
course_url
);
expect
(
view
.
$
(
'.course-details .course-text .course-key'
).
html
()).
toEqual
(
context
.
key
);
expect
(
view
.
$
(
'.course-details .course-text .run-period'
).
html
())
.
toEqual
(
context
.
run_modes
[
0
].
start_date
+
' - '
+
context
.
run_modes
[
0
].
end_date
);
};
beforeEach
(
function
()
{
...
...
@@ -58,22 +68,18 @@ define([
it
(
'should render the course card based on the data enrolled'
,
function
()
{
view
.
remove
();
setupView
(
true
);
expect
(
view
.
$
(
'.header-img'
).
attr
(
'src'
)).
toEqual
(
context
.
run_modes
[
0
].
course_image_url
);
expect
(
view
.
$
(
'.course-details .course-title-link'
).
text
().
trim
()).
toEqual
(
context
.
display_name
);
expect
(
view
.
$
(
'.course-details .course-title-link'
).
attr
(
'href'
)).
toEqual
(
context
.
run_modes
[
0
].
course_url
);
expect
(
view
.
$
(
'.course-details .course-text .course-key'
).
html
()).
toEqual
(
context
.
key
);
expect
(
view
.
$
(
'.course-details .course-text .run-period'
).
html
())
.
toEqual
(
context
.
run_modes
[
0
].
start_date
+
' - '
+
context
.
run_modes
[
0
].
end_date
);
validateCourseInfoDisplay
();
});
it
(
'should render the course card based on the data not enrolled'
,
function
()
{
expect
(
view
.
$
(
'.header-img'
).
attr
(
'src'
)).
toEqual
(
context
.
run_modes
[
0
].
course_image_url
);
expect
(
view
.
$
(
'.course-details .course-title-link'
).
text
().
trim
()).
toEqual
(
context
.
display_name
);
expect
(
view
.
$
(
'.course-details .course-title-link'
).
attr
(
'href'
)).
toEqual
(
context
.
run_modes
[
0
].
course_url
);
expect
(
view
.
$
(
'.course-details .course-text .course-key'
).
html
()).
toEqual
(
context
.
key
);
expect
(
view
.
$
(
'.course-details .course-text .run-period'
).
html
()).
not
.
toBeDefined
();
validateCourseInfoDisplay
();
});
it
(
'should update render if the course card is_enrolled updated'
,
function
(){
courseCardModel
.
set
({
is_enrolled
:
true
});
validateCourseInfoDisplay
();
});
});
}
...
...
lms/static/js/spec/learner_dashboard/course_enroll_view_spec.js
View file @
8d672bc8
This diff is collapsed.
Click to expand it.
lms/static/js/spec/learner_dashboard/program_details_header_spec.js
View file @
8d672bc8
...
...
@@ -8,9 +8,10 @@ define([
describe
(
'Program Details Header View'
,
function
()
{
var
view
=
null
,
programModel
,
context
=
{
programListingUrl
:
'/dashboard/programs'
,
urls
:
{
program_listing_url
:
'/dashboard/programs'
},
programData
:
{
uuid
:
'12-ab'
,
name
:
'Astrophysics'
,
...
...
@@ -58,7 +59,7 @@ define([
expect
(
view
.
$
(
'.org-logo'
).
attr
(
'alt'
)).
toEqual
(
context
.
programData
.
organizations
[
0
].
display_name
+
'
\'
s logo'
);
expect
(
programListUrl
).
toEqual
(
context
.
programListingU
rl
);
expect
(
programListUrl
).
toEqual
(
context
.
urls
.
program_listing_u
rl
);
});
});
}
...
...
lms/static/sass/elements/_course-card.scss
View file @
8d672bc8
.course-card
{
@include
span
(
10
);
margin-left
:
$baseline
*
2
+
px
;
margin-bottom
:
$baseline
+
px
;
margin-left
:
$baseline
*
2
;
margin-bottom
:
$baseline
;
.course-image-link
{
@include
float
(
left
);
.header-img
{
...
...
@@ -12,25 +12,34 @@
@include
float
(
right
);
width
:
100%
;
@include
susy-media
(
$bp-screen-sm
)
{
width
:
calc
(
100%
-
191px
);
}
padding-left
:
$baseline
*
1
.5
+
px
;
padding-left
:
$baseline
*
1
.5
;
.course-title
{
font-size
:
font-size
(
x-large
);
font-weight
:
font-weight
(
normal
);
margin-bottom
:
$baseline
/
4
+
px
;
margin-bottom
:
$baseline
/
4
;
}
.course-text
{
color
:
palette
(
grayscale
,
dark
);
.run-period
{
color
:
palette
(
grayscale
,
black
);
}
}
}
.course-actions
{
.enrollment-info
{
width
:
$baseline
*
10
+
px
;
width
:
$baseline
*
10
;
text-align
:
center
;
margin-bottom
:
$baseline
/
2
+
px
;
margin-bottom
:
$baseline
/
2
;
text-transform
:
uppercase
;
}
.run-select-container
{
margin-bottom
:
$baseline
;
.run-select
{
width
:
$baseline
*
10
;
}
}
.enroll-button
{
width
:
$baseline
*
10
+
px
;
width
:
$baseline
*
10
;
text-align
:
center
;
background-color
:
palette
(
success
,
dark
);
border-color
:
palette
(
success
,
dark
);
...
...
@@ -42,7 +51,7 @@
}
.view-course-link
{
width
:
$baseline
*
10
+
px
;
width
:
$baseline
*
10
;
text-align
:
center
;
}
}
...
...
lms/templates/learner_dashboard/course_card.underscore
View file @
8d672bc8
...
...
@@ -4,7 +4,7 @@
<img
class="header-img"
src="<%- course_image_url %>"
alt="<%= interpolate(gettext('%(courseName)s Home Page.'), {courseName: display_name}, true)%>"/>
alt="<%= interpolate(gettext('%(courseName)s Home Page.'), {courseName: display_name}, true)
%>"/>
</a>
<div class="course-details">
<h3 class="course-title">
...
...
@@ -13,9 +13,8 @@
</a>
</h3>
<div class="course-text">
<% if (is_enrolled){ %>
<span class="run-period"><%- start_date %> - <%- end_date %></span>
<% } %>
<span class="run-period"><%- start_date %> - <%- end_date %></span>
-
<span class="course-key"><%- key %></span>
</div>
</div>
...
...
lms/templates/learner_dashboard/course_enroll.underscore
View file @
8d672bc8
...
...
@@ -5,6 +5,27 @@
</a>
<% }else{ %>
<div class="enrollment-info"><%- gettext('not enrolled') %></div>
<% if (run_modes.length > 1){ %>
<div class="run-select-container">
<label class="sr-only" for="select-<%- course_key %>-run">Select Course Run</label>
<select id="select-<%- course_key %>-run" class="run-select" autocomplete="off">
<% _.each (run_modes, function(runMode){ %>
<option
value="<%- runMode.run_key %>"
<% if(run_key === runMode.run_key){ %>
selected="selected"
<% }%>
>
<%= interpolate(
gettext('Starts %(start)s'),
{ start: runMode.start_date },
true)
%>
</option>
<% }); %>
</select>
</div>
<% } %>
<button type="button" class="btn-brand btn enroll-button">
<%- gettext('Enroll Now') %>
</button>
...
...
lms/templates/learner_dashboard/program_details.html
View file @
8d672bc8
...
...
@@ -15,7 +15,7 @@ from openedx.core.djangolib.js_utils import (
<
%
static:require_module
module_name=
"js/learner_dashboard/program_details_factory"
class_name=
"ProgramDetailsFactory"
>
ProgramDetailsFactory({
programData: ${program_data | n, dump_js_escaped_json},
programListingUrl: '${program_listing_url | n, js_escaped_string}'
,
urls: ${urls | n, dump_js_escaped_json}
,
});
</
%
static:require
_module
>
</
%
block>
...
...
lms/templates/learner_dashboard/program_header_view.underscore
View file @
8d672bc8
...
...
@@ -29,7 +29,7 @@
<span class="crumb-separator fa fa-chevron-right" aria-hidden="true"></span>
</li>
<li class="crumb">
<a href="<%-
programListingU
rl %>" class="crumb-link"><%- gettext('Programs') %></a>
<a href="<%-
urls.program_listing_u
rl %>" class="crumb-link"><%- gettext('Programs') %></a>
<span class="crumb-separator fa fa-chevron-right" aria-hidden="true"></span>
</li>
<li class="crumb active">
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment