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
dafeca57
Unverified
Commit
dafeca57
authored
Sep 25, 2017
by
Gabe Mulley
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Change upgrade deadline messaging
parent
68d5fe89
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
268 additions
and
50 deletions
+268
-50
common/djangoapps/student/models.py
+21
-3
lms/djangoapps/courseware/course_tools.py
+73
-0
lms/djangoapps/courseware/date_summary.py
+50
-14
lms/djangoapps/courseware/tests/test_course_info.py
+2
-2
lms/djangoapps/courseware/tests/test_course_tools.py
+99
-0
lms/djangoapps/courseware/tests/test_date_summary.py
+10
-0
lms/djangoapps/courseware/tests/test_views.py
+2
-2
openedx/features/course_experience/tests/views/test_course_home.py
+2
-2
openedx/features/course_experience/views/course_sock.py
+7
-26
setup.py
+2
-1
No files found.
common/djangoapps/student/models.py
View file @
dafeca57
...
...
@@ -1709,13 +1709,25 @@ class CourseEnrollment(models.Model):
return
None
if
self
.
dynamic_upgrade_deadline
is
not
None
:
# When course modes expire they aren't found any more and None would be returned.
# Replicate that behavior here by returning None if the personalized deadline is in the past.
if
datetime
.
now
(
UTC
)
>=
self
.
dynamic_upgrade_deadline
:
return
None
return
self
.
dynamic_upgrade_deadline
return
self
.
course_upgrade_deadline
@cached_property
def
dynamic_upgrade_deadline
(
self
):
"""
Returns the learner's personalized upgrade deadline if one exists, otherwise it returns None.
Note that this will return a value even if the deadline is in the past. This property can be used
to modify behavior for users with personalized deadlines by checking if it's None or not.
Returns:
datetime|None
"""
try
:
course_overview
=
self
.
course
except
CourseOverview
.
DoesNotExist
:
...
...
@@ -1746,13 +1758,19 @@ class CourseEnrollment(models.Model):
log
.
debug
(
'Schedules: No schedule exists for CourseEnrollment
%
d.'
,
self
.
id
)
return
None
if
upgrade_deadline
is
None
or
datetime
.
now
(
UTC
)
>=
upgrade_deadline
:
return
None
return
upgrade_deadline
@cached_property
def
course_upgrade_deadline
(
self
):
"""
Returns the expiration datetime for the verified course mode.
If the mode is already expired, return None. Also return None if the course does not have a verified
course mode.
Returns:
datetime|None
"""
try
:
if
self
.
verified_mode
:
log
.
debug
(
'Schedules: Defaulting to verified mode expiration date-time for
%
s.'
,
self
.
course_id
)
...
...
lms/djangoapps/courseware/course_tools.py
0 → 100644
View file @
dafeca57
"""
Platform plugins to support a verified upgrade tool.
"""
import
datetime
import
pytz
from
django.utils.translation
import
ugettext
as
_
from
course_modes.models
import
CourseMode
from
openedx.features.course_experience.course_tools
import
CourseTool
from
student.models
import
CourseEnrollment
from
courseware.date_summary
import
verified_upgrade_deadline_link
from
request_cache
import
get_request
class
VerifiedUpgradeTool
(
CourseTool
):
"""
The verified upgrade tool.
"""
@classmethod
def
analytics_id
(
cls
):
"""
Returns an id to uniquely identify this tool in analytics events.
"""
return
'edx.tool.verified_upgrade'
@classmethod
def
is_enabled
(
cls
,
request
,
course_key
):
"""
Show this tool to all learners who are eligible to upgrade.
"""
enrollment
=
CourseEnrollment
.
get_enrollment
(
request
.
user
,
course_key
)
if
enrollment
is
None
:
return
False
if
enrollment
.
dynamic_upgrade_deadline
is
None
:
return
False
if
not
enrollment
.
is_active
:
return
False
if
enrollment
.
mode
not
in
CourseMode
.
UPSELL_TO_VERIFIED_MODES
:
return
False
if
enrollment
.
course_upgrade_deadline
is
None
:
return
False
if
datetime
.
datetime
.
now
(
pytz
.
UTC
)
>=
enrollment
.
course_upgrade_deadline
:
return
False
return
True
@classmethod
def
title
(
cls
):
"""
Returns the title of this tool.
"""
return
_
(
'Upgrade to Verified'
)
@classmethod
def
icon_classes
(
cls
):
"""
Returns the icon classes needed to represent this tool.
"""
return
'fa fa-certificate'
@classmethod
def
url
(
cls
,
course_key
):
"""
Returns the URL for this tool for the specified course key.
"""
request
=
get_request
()
return
verified_upgrade_deadline_link
(
request
.
user
,
course_id
=
course_key
)
lms/djangoapps/courseware/date_summary.py
View file @
dafeca57
...
...
@@ -10,6 +10,7 @@ from babel.dates import format_timedelta
from
django.conf
import
settings
from
django.core.urlresolvers
import
reverse
from
django.utils.formats
import
date_format
from
django.utils.functional
import
cached_property
from
django.utils.translation
import
get_language
,
to_locale
,
ugettext_lazy
from
django.utils.translation
import
ugettext
as
_
...
...
@@ -444,11 +445,6 @@ class VerifiedUpgradeDeadlineDate(DateSummary):
Verified track.
"""
css_class
=
'verified-upgrade-deadline'
title
=
ugettext_lazy
(
'Verification Upgrade Deadline'
)
description
=
ugettext_lazy
(
'You are still eligible to upgrade to a Verified Certificate! '
'Pursue it to highlight the knowledge and skills you gain in this course.'
)
link_text
=
ugettext_lazy
(
'Upgrade to Verified Certificate'
)
@property
...
...
@@ -475,12 +471,49 @@ class VerifiedUpgradeDeadlineDate(DateSummary):
@lazy
def
date
(
self
):
deadline
=
None
if
self
.
enrollment
:
deadline
=
self
.
enrollment
.
upgrade_deadline
return
self
.
enrollment
.
upgrade_deadline
else
:
return
None
@property
def
title
(
self
):
dynamic_deadline
=
self
.
_dynamic_deadline
()
if
dynamic_deadline
is
not
None
:
return
_
(
'Upgrade to Verified Certificate'
)
return
deadline
return
_
(
'Verification Upgrade Deadline'
)
def
_dynamic_deadline
(
self
):
if
not
self
.
enrollment
:
return
None
return
self
.
enrollment
.
dynamic_upgrade_deadline
@property
def
description
(
self
):
dynamic_deadline
=
self
.
_dynamic_deadline
()
if
dynamic_deadline
is
not
None
:
return
_
(
'Don
\'
t miss the opportunity to highlight your new knowledge and skills by earning a verified'
' certificate.'
)
return
_
(
'You are still eligible to upgrade to a Verified Certificate! '
'Pursue it to highlight the knowledge and skills you gain in this course.'
)
@property
def
relative_datestring
(
self
):
dynamic_deadline
=
self
.
_dynamic_deadline
()
if
dynamic_deadline
is
None
:
return
super
(
VerifiedUpgradeDeadlineDate
,
self
)
.
relative_datestring
if
self
.
date
is
None
or
self
.
deadline_has_passed
():
return
' '
# Translators: This describes the time by which the user
# should upgrade to the verified track. 'date' will be
# their personalized verified upgrade deadline formatted
# according to their locale.
return
_
(
u'by {date}'
)
def
register_alerts
(
self
,
request
,
course
):
"""
...
...
@@ -491,6 +524,13 @@ class VerifiedUpgradeDeadlineDate(DateSummary):
return
days_left_to_upgrade
=
(
self
.
date
-
self
.
current_time
)
.
days
if
self
.
date
>
self
.
current_time
and
days_left_to_upgrade
<=
settings
.
COURSE_MESSAGE_ALERT_DURATION_IN_DAYS
:
upgrade_message
=
_
(
"Don't forget, you have {time_remaining_string} left to upgrade to a Verified Certificate."
)
.
format
(
time_remaining_string
=
self
.
time_remaining_string
)
if
self
.
_dynamic_deadline
()
is
not
None
:
upgrade_message
=
_
(
"Don't forget to upgrade to a verified certificate by {localized_date}."
)
.
format
(
localized_date
=
date_format
(
self
.
date
))
CourseHomeMessages
.
register_info_message
(
request
,
Text
(
_
(
...
...
@@ -510,11 +550,7 @@ class VerifiedUpgradeDeadlineDate(DateSummary):
upgrade_label
=
Text
(
_
(
'Upgrade ({upgrade_price})'
))
.
format
(
upgrade_price
=
upgrade_price
),
)
),
title
=
Text
(
_
(
"Don't forget, you have {time_remaining_string} left to upgrade to a Verified Certificate."
))
.
format
(
time_remaining_string
=
self
.
time_remaining_string
,
)
title
=
Text
(
upgrade_message
)
)
...
...
lms/djangoapps/courseware/tests/test_course_info.py
View file @
dafeca57
...
...
@@ -379,7 +379,7 @@ class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTest
self
.
assertEqual
(
resp
.
status_code
,
200
)
def
test_num_queries_instructor_paced
(
self
):
self
.
fetch_course_info_with_queries
(
self
.
instructor_paced_course
,
2
6
,
3
)
self
.
fetch_course_info_with_queries
(
self
.
instructor_paced_course
,
2
7
,
3
)
def
test_num_queries_self_paced
(
self
):
self
.
fetch_course_info_with_queries
(
self
.
self_paced_course
,
2
6
,
3
)
self
.
fetch_course_info_with_queries
(
self
.
self_paced_course
,
2
7
,
3
)
lms/djangoapps/courseware/tests/test_course_tools.py
0 → 100644
View file @
dafeca57
import
datetime
from
mock
import
patch
from
nose.plugins.attrib
import
attr
import
pytz
from
django.test
import
RequestFactory
from
course_modes.models
import
CourseMode
from
course_modes.tests.factories
import
CourseModeFactory
from
courseware.course_tools
import
VerifiedUpgradeTool
from
courseware.models
import
DynamicUpgradeDeadlineConfiguration
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
openedx.core.djangoapps.schedules.config
import
CREATE_SCHEDULE_WAFFLE_FLAG
from
openedx.core.djangoapps.site_configuration.tests.factories
import
SiteFactory
from
openedx.core.djangoapps.waffle_utils.testutils
import
override_waffle_flag
from
student.tests.factories
import
CourseEnrollmentFactory
,
UserFactory
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
@attr
(
shard
=
3
)
class
VerifiedUpgradeToolTest
(
SharedModuleStoreTestCase
):
@classmethod
def
setUpClass
(
cls
):
super
(
VerifiedUpgradeToolTest
,
cls
)
.
setUpClass
()
cls
.
now
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
cls
.
course
=
CourseFactory
.
create
(
org
=
'edX'
,
number
=
'test'
,
display_name
=
'Test Course'
,
self_paced
=
True
,
start
=
cls
.
now
-
datetime
.
timedelta
(
days
=
30
),
)
cls
.
course_overview
=
CourseOverview
.
get_from_id
(
cls
.
course
.
id
)
@override_waffle_flag
(
CREATE_SCHEDULE_WAFFLE_FLAG
,
True
)
def
setUp
(
self
):
super
(
VerifiedUpgradeToolTest
,
self
)
.
setUp
()
self
.
course_verified_mode
=
CourseModeFactory
(
course_id
=
self
.
course
.
id
,
mode_slug
=
CourseMode
.
VERIFIED
,
expiration_datetime
=
self
.
now
+
datetime
.
timedelta
(
days
=
30
),
)
patcher
=
patch
(
'openedx.core.djangoapps.schedules.signals.get_current_site'
)
mock_get_current_site
=
patcher
.
start
()
self
.
addCleanup
(
patcher
.
stop
)
mock_get_current_site
.
return_value
=
SiteFactory
.
create
()
DynamicUpgradeDeadlineConfiguration
.
objects
.
create
(
enabled
=
True
)
self
.
enrollment
=
CourseEnrollmentFactory
(
course_id
=
self
.
course
.
id
,
mode
=
CourseMode
.
AUDIT
,
course
=
self
.
course_overview
,
)
self
.
request
=
RequestFactory
()
.
request
()
self
.
request
.
user
=
self
.
enrollment
.
user
def
test_tool_visible
(
self
):
self
.
assertTrue
(
VerifiedUpgradeTool
()
.
is_enabled
(
self
.
request
,
self
.
course
.
id
))
def
test_not_visible_when_no_enrollment_exists
(
self
):
self
.
enrollment
.
delete
()
request
=
RequestFactory
()
.
request
()
request
.
user
=
UserFactory
()
self
.
assertFalse
(
VerifiedUpgradeTool
()
.
is_enabled
(
self
.
request
,
self
.
course
.
id
))
def
test_not_visible_when_using_deadline_from_course_mode
(
self
):
DynamicUpgradeDeadlineConfiguration
.
objects
.
create
(
enabled
=
False
)
self
.
assertFalse
(
VerifiedUpgradeTool
()
.
is_enabled
(
self
.
request
,
self
.
course
.
id
))
def
test_not_visible_when_enrollment_is_inactive
(
self
):
self
.
enrollment
.
is_active
=
False
self
.
enrollment
.
save
()
self
.
assertFalse
(
VerifiedUpgradeTool
()
.
is_enabled
(
self
.
request
,
self
.
course
.
id
))
def
test_not_visible_when_already_verified
(
self
):
self
.
enrollment
.
mode
=
CourseMode
.
VERIFIED
self
.
enrollment
.
save
()
self
.
assertFalse
(
VerifiedUpgradeTool
()
.
is_enabled
(
self
.
request
,
self
.
course
.
id
))
def
test_not_visible_when_no_verified_track
(
self
):
self
.
course_verified_mode
.
delete
()
self
.
assertFalse
(
VerifiedUpgradeTool
()
.
is_enabled
(
self
.
request
,
self
.
course
.
id
))
def
test_not_visible_when_course_deadline_has_passed
(
self
):
self
.
course_verified_mode
.
expiration_datetime
=
self
.
now
-
datetime
.
timedelta
(
days
=
1
)
self
.
course_verified_mode
.
save
()
self
.
assertFalse
(
VerifiedUpgradeTool
()
.
is_enabled
(
self
.
request
,
self
.
course
.
id
))
def
test_not_visible_when_course_mode_has_no_deadline
(
self
):
self
.
course_verified_mode
.
expiration_datetime
=
None
self
.
course_verified_mode
.
save
()
self
.
assertFalse
(
VerifiedUpgradeTool
()
.
is_enabled
(
self
.
request
,
self
.
course
.
id
))
lms/djangoapps/courseware/tests/test_date_summary.py
View file @
dafeca57
...
...
@@ -585,6 +585,16 @@ class TestScheduleOverrides(SharedModuleStoreTestCase):
enrollment
=
CourseEnrollmentFactory
(
course_id
=
course
.
id
,
mode
=
CourseMode
.
AUDIT
)
block
=
VerifiedUpgradeDeadlineDate
(
course
,
enrollment
.
user
)
self
.
assertEqual
(
block
.
date
,
expected
)
self
.
_check_text
(
block
)
def
_check_text
(
self
,
upgrade_date_summary
):
self
.
assertEqual
(
upgrade_date_summary
.
title
,
'Upgrade to Verified Certificate'
)
self
.
assertEqual
(
upgrade_date_summary
.
description
,
'Don
\'
t miss the opportunity to highlight your new knowledge and skills by earning a verified'
' certificate.'
)
self
.
assertEqual
(
upgrade_date_summary
.
relative_datestring
,
'by {date}'
)
@override_waffle_flag
(
CREATE_SCHEDULE_WAFFLE_FLAG
,
True
)
def
test_date_with_self_paced_with_enrollment_after_course_start
(
self
):
...
...
lms/djangoapps/courseware/tests/test_views.py
View file @
dafeca57
...
...
@@ -213,8 +213,8 @@ class IndexQueryTestCase(ModuleStoreTestCase):
NUM_PROBLEMS
=
20
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
mongo
,
10
,
14
5
),
(
ModuleStoreEnum
.
Type
.
split
,
4
,
14
5
),
(
ModuleStoreEnum
.
Type
.
mongo
,
10
,
14
3
),
(
ModuleStoreEnum
.
Type
.
split
,
4
,
14
3
),
)
@ddt.unpack
def
test_index_query_counts
(
self
,
store_type
,
expected_mongo_query_count
,
expected_mysql_query_count
):
...
...
openedx/features/course_experience/tests/views/test_course_home.py
View file @
dafeca57
...
...
@@ -244,7 +244,7 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
expected_count
=
1
if
(
is_enrolled
or
is_unenrolled_staff
)
else
0
self
.
assertContains
(
response
,
TEST_CHAPTER_NAME
,
count
=
expected_count
)
self
.
assertContains
(
response
,
'Start Course'
,
count
=
expected_count
)
self
.
assertContains
(
response
,
'Learn About Verified Certificate'
,
count
=
expected_count
)
self
.
assertContains
(
response
,
'Learn About Verified Certificate'
,
count
=
(
1
if
is_enrolled
else
0
)
)
self
.
assertContains
(
response
,
TEST_WELCOME_MESSAGE
,
count
=
expected_count
)
# Verify that the expected message is shown to the user
...
...
@@ -285,7 +285,7 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
expected_count
=
1
if
(
is_enrolled
or
is_unenrolled_staff
)
else
0
self
.
assertContains
(
response
,
TEST_CHAPTER_NAME
,
count
=
expected_count
)
self
.
assertContains
(
response
,
'Start Course'
,
count
=
expected_count
)
self
.
assertContains
(
response
,
'Learn About Verified Certificate'
,
count
=
expected_count
)
self
.
assertContains
(
response
,
'Learn About Verified Certificate'
,
count
=
(
1
if
is_enrolled
else
0
)
)
# Verify that the expected message is shown to the user
self
.
assertContains
(
response
,
'<div class="user-messages">'
,
count
=
1
if
expected_message
else
0
)
...
...
openedx/features/course_experience/views/course_sock.py
View file @
dafeca57
...
...
@@ -3,12 +3,10 @@ Fragment for rendering the course's sock and associated toggle button.
"""
from
django.template.loader
import
render_to_string
from
django.utils.translation
import
get_language
from
opaque_keys.edx.keys
import
CourseKey
from
web_fragments.fragment
import
Fragment
from
commerce.utils
import
EcommerceService
from
course_modes.models
import
CourseMode
,
get_cosmetic_verified_display_price
from
courseware.date_summary
import
VerifiedUpgradeDeadlineDate
from
course_modes.models
import
get_cosmetic_verified_display_price
from
courseware.date_summary
import
verified_upgrade_deadline_link
,
verified_upgrade_link_is_valid
from
openedx.core.djangoapps.plugin_api.views
import
EdxFragmentView
from
student.models
import
CourseEnrollment
...
...
@@ -25,29 +23,12 @@ class CourseSockFragmentView(EdxFragmentView):
html
=
render_to_string
(
'course_experience/course-sock-fragment.html'
,
context
)
return
Fragment
(
html
)
def
get_verification_context
(
self
,
request
,
course
):
course_key
=
CourseKey
.
from_string
(
unicode
(
course
.
id
))
# Establish whether the course has a verified mode
available_modes
=
CourseMode
.
modes_for_course_dict
(
unicode
(
course
.
id
))
has_verified_mode
=
CourseMode
.
has_verified_mode
(
available_modes
)
# Establish whether the user is already enrolled
is_already_verified
=
CourseEnrollment
.
is_enrolled_as_verified
(
request
.
user
,
course_key
)
# Establish whether the verification deadline has already passed
verification_deadline
=
VerifiedUpgradeDeadlineDate
(
course
,
request
.
user
)
deadline_has_passed
=
verification_deadline
.
deadline_has_passed
()
# If this proves its worth, we can internationalize and display for more than English speakers.
show_course_sock
=
(
has_verified_mode
and
not
is_already_verified
and
not
deadline_has_passed
and
get_language
()
==
'en'
)
# Get information about the upgrade
@staticmethod
def
get_verification_context
(
request
,
course
):
enrollment
=
CourseEnrollment
.
get_enrollment
(
request
.
user
,
course
.
id
)
show_course_sock
=
verified_upgrade_link_is_valid
(
enrollment
)
and
get_language
()
==
'en'
upgrade_url
=
verified_upgrade_deadline_link
(
request
.
user
,
course
=
course
)
course_price
=
get_cosmetic_verified_display_price
(
course
)
upgrade_url
=
EcommerceService
()
.
upgrade_url
(
request
.
user
,
course_key
)
context
=
{
'show_course_sock'
:
show_course_sock
,
...
...
setup.py
View file @
dafeca57
...
...
@@ -6,7 +6,7 @@ from setuptools import setup
setup
(
name
=
"Open edX"
,
version
=
"0.
7
"
,
version
=
"0.
8
"
,
install_requires
=
[
"setuptools"
],
requires
=
[],
# NOTE: These are not the names we should be installing. This tree should
...
...
@@ -40,6 +40,7 @@ setup(
"course_bookmarks = openedx.features.course_bookmarks.plugins:CourseBookmarksTool"
,
"course_updates = openedx.features.course_experience.plugins:CourseUpdatesTool"
,
"course_reviews = openedx.features.course_experience.plugins:CourseReviewsTool"
,
"verified_upgrade = courseware.course_tools:VerifiedUpgradeTool"
,
],
"openedx.user_partition_scheme"
:
[
"random = openedx.core.djangoapps.user_api.partition_schemes:RandomUserPartitionScheme"
,
...
...
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