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
1210d3fc
Commit
1210d3fc
authored
Jul 02, 2016
by
Renzo Lucioni
Committed by
GitHub
Jul 02, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #12908 from edx/renzo/course-card-upgrade-cta
Add upgrade section to program detail course cards.
parents
69410948
d7381885
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
287 additions
and
90 deletions
+287
-90
lms/djangoapps/learner_dashboard/views.py
+3
-2
lms/static/js/learner_dashboard/models/course_card_model.js
+5
-4
lms/static/js/learner_dashboard/views/certificate_status_view.js
+2
-5
lms/static/js/learner_dashboard/views/course_card_view.js
+16
-3
lms/static/js/learner_dashboard/views/upgrade_message_view.js
+38
-0
lms/static/js/spec/learner_dashboard/course_card_view_spec.js
+62
-25
lms/static/sass/elements/_course-card.scss
+1
-0
lms/templates/learner_dashboard/certificate_status.underscore
+4
-2
lms/templates/learner_dashboard/course_card.underscore
+1
-0
lms/templates/learner_dashboard/upgrade_message.underscore
+12
-0
openedx/core/djangoapps/programs/tests/test_utils.py
+97
-30
openedx/core/djangoapps/programs/utils.py
+46
-19
No files found.
lms/djangoapps/learner_dashboard/views.py
View file @
1210d3fc
...
...
@@ -67,8 +67,9 @@ def program_details(request, program_id):
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'
)
reverse
(
'course_modes_choose'
,
kwargs
=
{
'course_id'
:
FAKE_COURSE_KEY
})
),
'commerce_api_url'
:
reverse
(
'commerce_api:v0:baskets:create'
),
}
context
=
{
...
...
lms/static/js/learner_dashboard/models/course_card_model.js
View file @
1210d3fc
...
...
@@ -65,17 +65,18 @@
course_key
:
runMode
.
course_key
,
course_url
:
runMode
.
course_url
||
''
,
display_name
:
this
.
context
.
display_name
,
start_date
:
runMode
.
start_date
,
end_date
:
runMode
.
end_date
,
enrollable_run_modes
:
this
.
getEnrollableRunModes
(),
enrollment_open_date
:
runMode
.
enrollment_open_date
||
''
,
is_course_ended
:
runMode
.
is_course_ended
,
is_enrolled
:
runMode
.
is_enrolled
,
is_enrollment_open
:
runMode
.
is_enrollment_open
,
key
:
this
.
context
.
key
,
marketing_url
:
runMode
.
marketing_url
||
''
,
is_course_ended
:
runMode
.
is_course_ended
,
mode_slug
:
runMode
.
mode_slug
,
run_key
:
runMode
.
run_key
,
enrollment_open_date
:
runMode
.
enrollment_open_date
||
''
,
enrollable_run_modes
:
this
.
getEnrollableRunModes
()
start_date
:
runMode
.
start_date
,
upgrade_url
:
runMode
.
upgrade_url
});
}
},
...
...
lms/static/js/learner_dashboard/views/certificate_status_view.js
View file @
1210d3fc
...
...
@@ -27,13 +27,10 @@
},
render
:
function
()
{
var
data
=
this
.
model
.
toJSON
(),
$icons
;
var
data
=
this
.
model
.
toJSON
();
data
=
$
.
extend
(
data
,
{
certificateSvg
:
this
.
iconTpl
()});
HtmlUtils
.
setHtml
(
this
.
$el
,
this
.
statusTpl
(
data
));
$icons
=
this
.
$
(
'.certificate-icon'
);
HtmlUtils
.
setHtml
(
$icons
,
this
.
iconTpl
());
}
});
}
...
...
lms/static/js/learner_dashboard/views/course_card_view.js
View file @
1210d3fc
...
...
@@ -7,6 +7,7 @@
'gettext'
,
'edx-ui-toolkit/js/utils/html-utils'
,
'js/learner_dashboard/models/course_enroll_model'
,
'js/learner_dashboard/views/upgrade_message_view'
,
'js/learner_dashboard/views/certificate_status_view'
,
'js/learner_dashboard/views/course_enroll_view'
,
'text!../../../templates/learner_dashboard/course_card.underscore'
...
...
@@ -18,6 +19,7 @@
gettext
,
HtmlUtils
,
EnrollModel
,
UpgradeMessageView
,
CertificateStatusView
,
CourseEnrollView
,
pageTpl
...
...
@@ -44,7 +46,8 @@
},
postRender
:
function
(){
var
$certStatus
=
this
.
$
(
'.certificate-status'
);
var
$upgradeMessage
=
this
.
$
(
'.upgrade-message'
),
$certStatus
=
this
.
$
(
'.certificate-status'
);
this
.
enrollView
=
new
CourseEnrollView
({
$parentEl
:
this
.
$
(
'.course-actions'
),
...
...
@@ -53,13 +56,23 @@
enrollModel
:
this
.
enrollModel
});
if
(
this
.
model
.
get
(
'certificate_url'
)
)
{
if
(
this
.
model
.
get
(
'upgrade_url'
)
)
{
this
.
upgradeMessage
=
new
UpgradeMessageView
({
$el
:
$upgradeMessage
,
model
:
this
.
model
});
$certStatus
.
remove
();
}
else
if
(
this
.
model
.
get
(
'certificate_url'
)
)
{
this
.
certificateStatus
=
new
CertificateStatusView
({
$el
:
$certStatus
,
model
:
this
.
model
});
$upgradeMessage
.
remove
();
}
else
{
// Styles are applied to the element that show if it's empty
// Styles are applied to these elements which will be visible if they're empty.
$upgradeMessage
.
remove
();
$certStatus
.
remove
();
}
}
...
...
lms/static/js/learner_dashboard/views/upgrade_message_view.js
0 → 100644
View file @
1210d3fc
;(
function
(
define
)
{
'use strict'
;
define
([
'backbone'
,
'jquery'
,
'underscore'
,
'gettext'
,
'edx-ui-toolkit/js/utils/html-utils'
,
'text!../../../templates/learner_dashboard/upgrade_message.underscore'
,
'text!../../../templates/learner_dashboard/certificate_icon.underscore'
],
function
(
Backbone
,
$
,
_
,
gettext
,
HtmlUtils
,
upgradeMessageTpl
,
certificateIconTpl
)
{
return
Backbone
.
View
.
extend
({
messageTpl
:
HtmlUtils
.
template
(
upgradeMessageTpl
),
iconTpl
:
HtmlUtils
.
template
(
certificateIconTpl
),
initialize
:
function
(
options
)
{
this
.
$el
=
options
.
$el
;
this
.
render
();
},
render
:
function
()
{
var
data
=
this
.
model
.
toJSON
();
data
=
$
.
extend
(
data
,
{
certificateSvg
:
this
.
iconTpl
()});
HtmlUtils
.
setHtml
(
this
.
$el
,
this
.
messageTpl
(
data
));
}
});
}
);
}).
call
(
this
,
define
||
RequireJS
.
define
);
lms/static/js/spec/learner_dashboard/course_card_view_spec.js
View file @
1210d3fc
...
...
@@ -19,27 +19,30 @@ define([
key
:
'ANUx'
},
run_modes
:
[{
start_date
:
'Apr 25, 2016
'
,
end_date
:
'Jun 13, 2019
'
,
certificate_url
:
'
'
,
course_image_url
:
'http://test.com/image1
'
,
course_key
:
'course-v1:ANUx+ANU-ASTRO1x+3T2015'
,
course_started
:
true
,
course_url
:
'http://localhost:8000/courses/course-v1:edX+DemoX+Demo_Course/info'
,
end_date
:
'Jun 13, 2019'
,
enrollment_open_date
:
'Mar 03, 2016'
,
is_course_ended
:
false
,
is_enrolled
:
true
,
is_enrollment_open
:
true
,
marketing_url
:
'https://www.edx.org/course/astrophysics-exploring'
,
course_image_url
:
'http://test.com/image1'
,
mode_slug
:
'verified'
,
run_key
:
'2T2016'
,
course_started
:
true
,
is_enrolled
:
true
,
is_course_ended
:
false
,
is_enrollment_open
:
true
,
certificate_url
:
''
,
enrollment_open_date
:
'Mar 03, 2016'
start_date
:
'Apr 25, 2016'
,
upgrade_url
:
''
}]
},
setupView
=
function
(
data
,
isEnrolled
){
data
.
run_modes
[
0
].
is_enrolled
=
isEnrolled
;
var
programData
=
$
.
extend
({},
data
);
programData
.
run_modes
[
0
].
is_enrolled
=
isEnrolled
;
setFixtures
(
'<div class="course-card card"></div>'
);
courseCardModel
=
new
CourseCardModel
(
d
ata
);
courseCardModel
=
new
CourseCardModel
(
programD
ata
);
view
=
new
CourseCardView
({
model
:
courseCardModel
});
...
...
@@ -86,37 +89,71 @@ define([
});
it
(
'should only show certificate status section if a certificate has been earned'
,
function
()
{
var
data
=
context
,
var
data
=
$
.
extend
({},
context
)
,
certUrl
=
'sample-certificate'
;
setupView
(
context
,
false
);
expect
(
view
.
$
(
'certificate-status'
).
length
).
toEqual
(
0
);
expect
(
view
.
$
(
'.certificate-status'
).
length
).
toEqual
(
0
);
view
.
remove
();
data
.
run_modes
[
0
].
certificate_url
=
certUrl
;
setupView
(
data
,
false
);
expect
(
view
.
$
(
'.certificate-status'
).
length
).
toEqual
(
1
);
expect
(
view
.
$
(
'.certificate-status .cta-secondary'
).
attr
(
'href'
)).
toEqual
(
certUrl
);
});
it
(
'should render the course card with coming soon'
,
function
(){
it
(
'should only show upgrade message section if an upgrade is required'
,
function
()
{
var
data
=
$
.
extend
({},
context
),
upgradeUrl
=
'/path/to/upgrade'
;
expect
(
view
.
$
(
'.upgrade-message'
).
length
).
toEqual
(
0
);
view
.
remove
();
context
.
run_modes
[
0
].
is_enrollment_open
=
false
;
setupView
(
context
,
false
);
expect
(
view
.
$
(
'.header-img'
).
attr
(
'src'
)).
toEqual
(
context
.
run_modes
[
0
].
course_image_url
);
expect
(
view
.
$
(
'.course-details .course-title'
).
text
().
trim
()).
toEqual
(
context
.
display_name
);
data
.
run_modes
[
0
].
upgrade_url
=
upgradeUrl
;
setupView
(
data
,
false
);
expect
(
view
.
$
(
'.upgrade-message'
).
length
).
toEqual
(
1
);
expect
(
view
.
$
(
'.upgrade-message .cta-primary'
).
attr
(
'href'
)).
toEqual
(
upgradeUrl
);
});
it
(
'should not show both the upgrade message and certificate status sections'
,
function
()
{
var
data
=
$
.
extend
({},
context
);
// Verify that no empty elements are left in the DOM.
data
.
run_modes
[
0
].
upgrade_url
=
''
;
data
.
run_modes
[
0
].
certificate_url
=
''
;
setupView
(
data
,
false
);
expect
(
view
.
$
(
'.upgrade-message'
).
length
).
toEqual
(
0
);
expect
(
view
.
$
(
'.certificate-status'
).
length
).
toEqual
(
0
);
view
.
remove
();
// Verify that the upgrade message takes priority.
data
.
run_modes
[
0
].
upgrade_url
=
'/path/to/upgrade'
;
data
.
run_modes
[
0
].
certificate_url
=
'/path/to/certificate'
;
setupView
(
data
,
false
);
expect
(
view
.
$
(
'.upgrade-message'
).
length
).
toEqual
(
1
);
expect
(
view
.
$
(
'.certificate-status'
).
length
).
toEqual
(
0
);
});
it
(
'should render the course card with coming soon'
,
function
(){
var
data
=
$
.
extend
({},
context
);
data
.
run_modes
[
0
].
is_enrollment_open
=
false
;
setupView
(
data
,
false
);
expect
(
view
.
$
(
'.header-img'
).
attr
(
'src'
)).
toEqual
(
data
.
run_modes
[
0
].
course_image_url
);
expect
(
view
.
$
(
'.course-details .course-title'
).
text
().
trim
()).
toEqual
(
data
.
display_name
);
expect
(
view
.
$
(
'.course-details .course-title-link'
).
length
).
toBe
(
0
);
expect
(
view
.
$
(
'.course-details .course-text .course-key'
).
html
()).
toEqual
(
context
.
key
);
expect
(
view
.
$
(
'.course-details .course-text .course-key'
).
html
()).
toEqual
(
data
.
key
);
expect
(
view
.
$
(
'.course-details .course-text .run-period'
).
length
).
toBe
(
0
);
expect
(
view
.
$
(
'.no-action-message'
).
text
().
trim
()).
toBe
(
'Coming Soon'
);
expect
(
view
.
$
(
'.enrollment-open-date'
).
text
().
trim
())
.
toEqual
(
context
.
run_modes
[
0
].
enrollment_open_date
);
.
toEqual
(
data
.
run_modes
[
0
].
enrollment_open_date
);
});
it
(
'should render if enrollment_open_date is not provided'
,
function
(){
view
.
remove
();
context
.
run_modes
[
0
].
is_enrollment_open
=
true
;
delete
context
.
run_modes
[
0
].
enrollment_open_date
;
setupView
(
context
,
false
);
var
data
=
$
.
extend
({},
context
);
data
.
run_modes
[
0
].
is_enrollment_open
=
true
;
delete
data
.
run_modes
[
0
].
enrollment_open_date
;
setupView
(
data
,
false
);
validateCourseInfoDisplay
();
});
});
...
...
lms/static/sass/elements/_course-card.scss
View file @
1210d3fc
...
...
@@ -128,6 +128,7 @@
}
}
.upgrade-message
,
.certificate-status
{
border-top
:
1px
solid
palette
(
grayscale
,
x-trans
);
padding-top
:
$baseline
;
...
...
lms/templates/learner_dashboard/certificate_status.underscore
View file @
1210d3fc
<div class="message col-12 md-col-8">
<span class="certificate-icon green-certificate-icon" aria-hidden="true"></span>
<% // safe-lint: disable=underscore-not-escaped %>
<span class="certificate-icon green-certificate-icon" aria-hidden="true"><%= certificateSvg %></span>
<span class="card-msg"><%- gettext('Congratulations! You have earned a certificate for this course.') %></span>
</div>
<div class="action col-12 md-col-4">
<a href="<%- certificate_url %>" class="btn-brand cta-secondary">
<span class="certificate-icon blue-certificate-icon" aria-hidden="true"></span>
<% // safe-lint: disable=underscore-not-escaped %>
<span class="certificate-icon blue-certificate-icon" aria-hidden="true"><%= certificateSvg %></span>
<%- gettext('View/Share Certificate') %>
</a>
</div>
lms/templates/learner_dashboard/course_card.underscore
View file @
1210d3fc
...
...
@@ -39,4 +39,5 @@
</div>
</div>
<div class="section action-msg-view"></div>
<div class="section upgrade-message"></div>
<div class="section certificate-status"></div>
lms/templates/learner_dashboard/upgrade_message.underscore
0 → 100644
View file @
1210d3fc
<div class="message col-12 md-col-8">
<% // safe-lint: disable=underscore-not-escaped %>
<span class="certificate-icon green-certificate-icon" aria-hidden="true"><%= certificateSvg %></span>
<span class="card-msg"><%- gettext('You need a certificate in this course to be eligible for a program certificate.') %></span>
</div>
<div class="action col-12 md-col-4">
<a href="<%- upgrade_url %>" class="btn-brand cta-primary">
<% // safe-lint: disable=underscore-not-escaped %>
<span class="certificate-icon green-certificate-icon" aria-hidden="true"><%= certificateSvg %></span>
<%- gettext('Upgrade Now') %>
</a>
</div>
openedx/core/djangoapps/programs/tests/test_utils.py
View file @
1210d3fc
...
...
@@ -10,6 +10,7 @@ from django.conf import settings
from
django.core.cache
import
cache
from
django.core.urlresolvers
import
reverse
from
django.test
import
TestCase
from
django.test.utils
import
override_settings
from
django.utils
import
timezone
import
httpretty
import
mock
...
...
@@ -18,6 +19,7 @@ from edx_oauth2_provider.tests.factories import ClientFactory
from
provider.constants
import
CONFIDENTIAL
from
lms.djangoapps.certificates.api
import
MODES
from
lms.djangoapps.commerce.tests.test_utils
import
update_commerce_config
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
openedx.core.djangoapps.credentials.tests
import
factories
as
credentials_factories
from
openedx.core.djangoapps.credentials.tests.mixins
import
CredentialsApiConfigMixin
,
CredentialsDataMixin
...
...
@@ -34,6 +36,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
UTILS_MODULE
=
'openedx.core.djangoapps.programs.utils'
CERTIFICATES_API_MODULE
=
'lms.djangoapps.certificates.api'
ECOMMERCE_URL_ROOT
=
'http://example-ecommerce.com'
@skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
...
...
@@ -672,11 +675,14 @@ class TestProgramProgressMeter(ProgramsApiConfigMixin, TestCase):
@ddt.ddt
@override_settings
(
ECOMMERCE_PUBLIC_URL_ROOT
=
ECOMMERCE_URL_ROOT
)
@skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
class
TestSupplementProgramData
(
ProgramsApiConfigMixin
,
ModuleStoreTestCase
):
"""Tests of the utility function used to supplement program data."""
password
=
'test'
maxDiff
=
None
sku
=
'abc123'
password
=
'test'
checkout_path
=
'/basket'
def
setUp
(
self
):
super
(
TestSupplementProgramData
,
self
)
.
setUp
()
...
...
@@ -704,15 +710,18 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
course_overview
=
CourseOverview
.
get_from_id
(
self
.
course
.
id
)
# pylint: disable=no-member
run_mode
=
dict
(
factories
.
RunMode
(
certificate_url
=
None
,
course_image_url
=
course_overview
.
course_image_url
,
course_key
=
unicode
(
self
.
course
.
id
),
# pylint: disable=no-member
course_url
=
reverse
(
'course_root'
,
args
=
[
self
.
course
.
id
]),
# pylint: disable=no-member
course_image_url
=
course_overview
.
course_image_url
,
start_date
=
strftime_localized
(
self
.
course
.
start
,
'SHORT_DATE'
),
end_date
=
strftime_localized
(
self
.
course
.
end
,
'SHORT_DATE'
),
enrollment_open_date
=
None
,
is_course_ended
=
self
.
course
.
end
<
timezone
.
now
(),
is_enrolled
=
False
,
is_enrollment_open
=
True
,
marketing_url
=
''
,
marketing_url
=
None
,
start_date
=
strftime_localized
(
self
.
course
.
start
,
'SHORT_DATE'
),
upgrade_url
=
None
,
),
**
kwargs
)
...
...
@@ -722,19 +731,72 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
self
.
assertEqual
(
actual
,
expected
)
@ddt.data
(
True
,
False
)
def
test_student_enrollment_status
(
self
,
is_enrolled
):
"""Verify that program data is supplemented correctly."""
@ddt.data
(
(
False
,
None
,
False
),
(
True
,
MODES
.
audit
,
True
),
(
True
,
MODES
.
verified
,
False
),
)
@ddt.unpack
@mock.patch
(
UTILS_MODULE
+
'.CourseMode.mode_for_course'
)
def
test_student_enrollment_status
(
self
,
is_enrolled
,
enrolled_mode
,
is_upgrade_required
,
mock_get_mode
):
"""Verify that program data is supplemented with the student's enrollment status."""
expected_upgrade_url
=
'{root}/{path}?sku={sku}'
.
format
(
root
=
ECOMMERCE_URL_ROOT
,
path
=
self
.
checkout_path
.
strip
(
'/'
),
sku
=
self
.
sku
,
)
update_commerce_config
(
enabled
=
True
,
checkout_page
=
self
.
checkout_path
)
mock_mode
=
mock
.
Mock
()
mock_mode
.
sku
=
self
.
sku
mock_get_mode
.
return_value
=
mock_mode
if
is_enrolled
:
CourseEnrollmentFactory
(
user
=
self
.
user
,
course_id
=
self
.
course
.
id
)
# pylint: disable=no-member
CourseEnrollmentFactory
(
user
=
self
.
user
,
course_id
=
self
.
course
.
id
,
mode
=
enrolled_mode
)
# pylint: disable=no-member
data
=
utils
.
supplement_program_data
(
self
.
program
,
self
.
user
)
self
.
_assert_supplemented
(
data
,
is_enrolled
=
is_enrolled
)
self
.
_assert_supplemented
(
data
,
is_enrolled
=
is_enrolled
,
upgrade_url
=
expected_upgrade_url
if
is_upgrade_required
else
None
)
@ddt.data
(
MODES
.
audit
,
MODES
.
verified
)
def
test_inactive_enrollment_no_upgrade
(
self
,
enrolled_mode
):
"""Verify that a student with an inactive enrollment isn't encouraged to upgrade."""
update_commerce_config
(
enabled
=
True
,
checkout_page
=
self
.
checkout_path
)
CourseEnrollmentFactory
(
user
=
self
.
user
,
course_id
=
self
.
course
.
id
,
# pylint: disable=no-member
mode
=
enrolled_mode
,
is_active
=
False
,
)
data
=
utils
.
supplement_program_data
(
self
.
program
,
self
.
user
)
self
.
_assert_supplemented
(
data
)
@mock.patch
(
UTILS_MODULE
+
'.CourseMode.mode_for_course'
)
def
test_ecommerce_disabled
(
self
,
mock_get_mode
):
"""Verify that the utility can operate when the ecommerce service is disabled."""
update_commerce_config
(
enabled
=
False
,
checkout_page
=
self
.
checkout_path
)
mock_mode
=
mock
.
Mock
()
mock_mode
.
sku
=
self
.
sku
mock_get_mode
.
return_value
=
mock_mode
CourseEnrollmentFactory
(
user
=
self
.
user
,
course_id
=
self
.
course
.
id
,
mode
=
MODES
.
audit
)
# pylint: disable=no-member
data
=
utils
.
supplement_program_data
(
self
.
program
,
self
.
user
)
self
.
_assert_supplemented
(
data
,
is_enrolled
=
True
,
upgrade_url
=
None
)
@ddt.data
(
[
1
,
1
,
False
]
,
[
1
,
-
1
,
True
]
,
(
1
,
1
,
False
)
,
(
1
,
-
1
,
True
)
,
)
@ddt.unpack
def
test_course_enrollment_status
(
self
,
start_offset
,
end_offset
,
is_enrollment_open
):
...
...
@@ -746,13 +808,15 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
data
=
utils
.
supplement_program_data
(
self
.
program
,
self
.
user
)
if
is_enrollment_open
:
self
.
_assert_supplemented
(
data
,
is_enrollment_open
=
is_enrollment_open
)
enrollment_open_date
=
None
else
:
self
.
_assert_supplemented
(
data
,
is_enrollment_open
=
is_enrollment_open
,
enrollment_open_date
=
strftime_localized
(
self
.
course
.
enrollment_start
,
'SHORT_DATE'
)
)
enrollment_open_date
=
strftime_localized
(
self
.
course
.
enrollment_start
,
'SHORT_DATE'
)
self
.
_assert_supplemented
(
data
,
is_enrollment_open
=
is_enrollment_open
,
enrollment_open_date
=
enrollment_open_date
,
)
@ddt.data
(
True
,
False
)
@mock.patch
(
UTILS_MODULE
+
'.certificate_api.certificate_downloadable_status'
)
...
...
@@ -765,11 +829,21 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
data
=
utils
.
supplement_program_data
(
self
.
program
,
self
.
user
)
if
is_uuid_available
:
expected_url
=
reverse
(
'certificates:render_cert_by_uuid'
,
kwargs
=
{
'certificate_uuid'
:
test_uuid
})
self
.
_assert_supplemented
(
data
,
certificate_url
=
expected_url
)
else
:
self
.
_assert_supplemented
(
data
)
expected_url
=
reverse
(
'certificates:render_cert_by_uuid'
,
kwargs
=
{
'certificate_uuid'
:
test_uuid
}
)
if
is_uuid_available
else
None
self
.
_assert_supplemented
(
data
,
certificate_url
=
expected_url
)
@ddt.data
(
-
1
,
0
,
1
)
def
test_course_course_ended
(
self
,
days_offset
):
self
.
course
.
end
=
timezone
.
now
()
+
datetime
.
timedelta
(
days
=
days_offset
)
self
.
course
=
self
.
update_course
(
self
.
course
,
self
.
user
.
id
)
# pylint: disable=no-member
data
=
utils
.
supplement_program_data
(
self
.
program
,
self
.
user
)
self
.
_assert_supplemented
(
data
)
@mock.patch
(
UTILS_MODULE
+
'.get_organization_by_short_name'
)
def
test_organization_logo_exists
(
self
,
mock_get_organization_by_short_name
):
...
...
@@ -780,6 +854,7 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
mock_get_organization_by_short_name
.
return_value
=
{
'logo'
:
mock_image
}
data
=
utils
.
supplement_program_data
(
self
.
program
,
self
.
user
)
self
.
assertEqual
(
data
[
'organizations'
][
0
]
.
get
(
'img'
),
mock_logo_url
)
...
...
@@ -799,11 +874,3 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
mock_get_organization_by_short_name
.
return_value
=
{
'logo'
:
None
}
data
=
utils
.
supplement_program_data
(
self
.
program
,
self
.
user
)
self
.
assertEqual
(
data
[
'organizations'
][
0
]
.
get
(
'img'
),
None
)
@ddt.data
(
-
1
,
0
,
1
)
def
test_course_course_ended
(
self
,
days_offset
):
self
.
course
.
end
=
timezone
.
now
()
+
datetime
.
timedelta
(
days
=
days_offset
)
self
.
course
=
self
.
update_course
(
self
.
course
,
self
.
user
.
id
)
# pylint: disable=no-member
data
=
utils
.
supplement_program_data
(
self
.
program
,
self
.
user
)
self
.
_assert_supplemented
(
data
)
openedx/core/djangoapps/programs/utils.py
View file @
1210d3fc
...
...
@@ -10,7 +10,9 @@ from django.utils.text import slugify
from
opaque_keys.edx.keys
import
CourseKey
import
pytz
from
course_modes.models
import
CourseMode
from
lms.djangoapps.certificates
import
api
as
certificate_api
from
lms.djangoapps.commerce.utils
import
EcommerceService
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
openedx.core.djangoapps.programs.models
import
ProgramsApiConfig
from
openedx.core.lib.edx_api_utils
import
get_edx_api_data
...
...
@@ -322,6 +324,7 @@ class ProgramProgressMeter(object):
return
parsed
# TODO: This function will benefit from being refactored as a class.
def
supplement_program_data
(
program_data
,
user
):
"""Supplement program course codes with CourseOverview and CourseEnrollment data.
...
...
@@ -330,8 +333,8 @@ def supplement_program_data(program_data, user):
user (User): The user whose enrollments to inspect.
"""
for
organization
in
program_data
[
'organizations'
]:
# TODO
cache the results of the get_organization_by_short_name call
#
so we don't have to hit database that frequently
# TODO
: Cache the results of the get_organization_by_short_name call so
#
the database is hit less frequently.
org_obj
=
get_organization_by_short_name
(
organization
[
'key'
])
if
org_obj
and
org_obj
.
get
(
'logo'
):
organization
[
'img'
]
=
org_obj
[
'logo'
]
.
url
...
...
@@ -341,34 +344,58 @@ def supplement_program_data(program_data, user):
course_key
=
CourseKey
.
from_string
(
run_mode
[
'course_key'
])
course_overview
=
CourseOverview
.
get_from_id
(
course_key
)
run_mode
[
'course_url'
]
=
reverse
(
'course_root'
,
args
=
[
course_key
])
run_mode
[
'course_image_url'
]
=
course_overview
.
course_image_url
course_url
=
reverse
(
'course_root'
,
args
=
[
course_key
])
course_image_url
=
course_overview
.
course_image_url
run_mode
[
'start_date'
]
=
course_overview
.
start_datetime_text
()
run_mode
[
'end_date'
]
=
course_overview
.
end_datetime_text
()
start_date_string
=
course_overview
.
start_datetime_text
()
end_date_string
=
course_overview
.
end_datetime_text
()
end_date
=
course_overview
.
end
or
datetime
.
datetime
.
max
.
replace
(
tzinfo
=
pytz
.
UTC
)
run_mode
[
'is_course_ended'
]
=
end_date
<
timezone
.
now
()
is_course_ended
=
end_date
<
timezone
.
now
()
run_mode
[
'is_enrolled'
]
=
CourseEnrollment
.
is_enrolled
(
user
,
course_key
)
is_enrolled
=
CourseEnrollment
.
is_enrolled
(
user
,
course_key
)
enrollment_start
=
course_overview
.
enrollment_start
or
datetime
.
datetime
.
min
.
replace
(
tzinfo
=
pytz
.
UTC
)
enrollment_end
=
course_overview
.
enrollment_end
or
datetime
.
datetime
.
max
.
replace
(
tzinfo
=
pytz
.
UTC
)
is_enrollment_open
=
enrollment_start
<=
timezone
.
now
()
<
enrollment_end
run_mode
[
'is_enrollment_open'
]
=
is_enrollment_open
if
not
is_enrollment_open
:
# Only render this enrollment open date if the enrollment open is in the future
run_mode
[
'enrollment_open_date'
]
=
strftime_localized
(
enrollment_start
,
'SHORT_DATE'
)
# TODO: Currently unavailable on LMS.
run_mode
[
'marketing_url'
]
=
''
enrollment_open_date
=
None
if
is_enrollment_open
else
strftime_localized
(
enrollment_start
,
'SHORT_DATE'
)
certificate_data
=
certificate_api
.
certificate_downloadable_status
(
user
,
course_key
)
certificate_uuid
=
certificate_data
.
get
(
'uuid'
)
if
certificate_uuid
:
run_mode
[
'certificate_url'
]
=
certificate_api
.
get_certificate_url
(
course_id
=
course_key
,
uuid
=
certificate_uuid
,
)
certificate_url
=
certificate_api
.
get_certificate_url
(
course_id
=
course_key
,
uuid
=
certificate_uuid
,
)
if
certificate_uuid
else
None
required_mode_slug
=
run_mode
[
'mode_slug'
]
enrolled_mode_slug
,
_
=
CourseEnrollment
.
enrollment_mode_for_user
(
user
,
course_key
)
is_mode_mismatch
=
required_mode_slug
!=
enrolled_mode_slug
is_upgrade_required
=
is_enrolled
and
is_mode_mismatch
# Requires that the ecommerce service be in use.
required_mode
=
CourseMode
.
mode_for_course
(
course_key
,
required_mode_slug
)
ecommerce
=
EcommerceService
()
sku
=
getattr
(
required_mode
,
'sku'
,
None
)
if
ecommerce
.
is_enabled
(
user
)
and
sku
:
upgrade_url
=
ecommerce
.
checkout_page_url
(
required_mode
.
sku
)
if
is_upgrade_required
else
None
else
:
upgrade_url
=
None
run_mode
.
update
({
'certificate_url'
:
certificate_url
,
'course_image_url'
:
course_image_url
,
'course_url'
:
course_url
,
'end_date'
:
end_date_string
,
'enrollment_open_date'
:
enrollment_open_date
,
'is_course_ended'
:
is_course_ended
,
'is_enrolled'
:
is_enrolled
,
'is_enrollment_open'
:
is_enrollment_open
,
# TODO: Not currently available on LMS.
'marketing_url'
:
None
,
'start_date'
:
start_date_string
,
'upgrade_url'
:
upgrade_url
,
})
return
program_data
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