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
8d3fa543
Commit
8d3fa543
authored
Jul 26, 2017
by
Diana Huang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add in-place enrollment to course home page.
parent
4f5b033c
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
186 additions
and
5 deletions
+186
-5
lms/djangoapps/courseware/courses.py
+25
-0
lms/djangoapps/courseware/tests/test_courses.py
+49
-0
lms/djangoapps/courseware/views/views.py
+13
-5
openedx/features/course_experience/static/course_experience/fixtures/enrollment-button.html
+1
-0
openedx/features/course_experience/static/course_experience/js/Enrollment.js
+45
-0
openedx/features/course_experience/static/course_experience/js/spec/Enrollment_spec.js
+48
-0
openedx/features/course_experience/templates/course_experience/course-home-fragment.html
+4
-0
webpack.config.js
+1
-0
No files found.
lms/djangoapps/courseware/courses.py
View file @
8d3fa543
...
...
@@ -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.
...
...
lms/djangoapps/courseware/tests/test_courses.py
View file @
8d3fa543
...
...
@@ -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
):
...
...
lms/djangoapps/courseware/views/views.py
View file @
8d3fa543
...
...
@@ -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,14 +450,21 @@ class CourseTabView(EdxFragmentView):
)
)
elif
not
is_enrolled
and
not
is_staff
:
# 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
(
_
(
'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"
),
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
...
...
openedx/features/course_experience/static/course_experience/fixtures/enrollment-button.html
0 → 100644
View file @
8d3fa543
<button
class=
"enroll-btn btn-link"
>
Enroll Now
</button>
openedx/features/course_experience/static/course_experience/js/Enrollment.js
0 → 100644
View file @
8d3fa543
/*
* 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
));
}
}
openedx/features/course_experience/static/course_experience/js/spec/Enrollment_spec.js
0 → 100644
View file @
8d3fa543
/* 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
();
});
});
});
openedx/features/course_experience/templates/course_experience/course-home-fragment.html
View file @
8d3fa543
...
...
@@ -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>
webpack.config.js
View file @
8d3fa543
...
...
@@ -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'
},
...
...
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