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
0e58e7cb
Commit
0e58e7cb
authored
Dec 18, 2014
by
Stephen Sanchez
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #6216 from edx/sanchez/cleanup-restful-layer
Enrollment API Cleanup
parents
7a227bb2
22a18e35
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
572 additions
and
261 deletions
+572
-261
common/djangoapps/enrollment/api.py
+35
-103
common/djangoapps/enrollment/data.py
+83
-26
common/djangoapps/enrollment/errors.py
+48
-0
common/djangoapps/enrollment/serializers.py
+7
-3
common/djangoapps/enrollment/tests/fake_data_api.py
+7
-4
common/djangoapps/enrollment/tests/test_api.py
+6
-5
common/djangoapps/enrollment/tests/test_data.py
+50
-12
common/djangoapps/enrollment/tests/test_views.py
+116
-22
common/djangoapps/enrollment/urls.py
+20
-12
common/djangoapps/enrollment/views.py
+182
-59
lms/static/js/spec/student_account/enrollment_spec.js
+7
-2
lms/static/js/student_account/enrollment.js
+10
-12
lms/urls.py
+1
-1
No files found.
common/djangoapps/enrollment/api.py
View file @
0e58e7cb
...
...
@@ -6,50 +6,24 @@ course level, such as available course modes.
from
django.utils
import
importlib
import
logging
from
django.conf
import
settings
from
enrollment
import
errors
log
=
logging
.
getLogger
(
__name__
)
class
CourseEnrollmentError
(
Exception
):
"""Generic Course Enrollment Error.
Describes any error that may occur when reading or updating enrollment information for a student or a course.
"""
def
__init__
(
self
,
msg
,
data
=
None
):
super
(
CourseEnrollmentError
,
self
)
.
__init__
(
msg
)
# Corresponding information to help resolve the error.
self
.
data
=
data
class
CourseModeNotFoundError
(
CourseEnrollmentError
):
"""The requested course mode could not be found."""
pass
class
EnrollmentNotFoundError
(
CourseEnrollmentError
):
"""The requested enrollment could not be found."""
pass
class
EnrollmentApiLoadError
(
CourseEnrollmentError
):
"""The data API could not be loaded."""
pass
DEFAULT_DATA_API
=
'enrollment.data'
def
get_enrollments
(
student
_id
):
"""Retrieves all the courses a
student
is enrolled in.
def
get_enrollments
(
user
_id
):
"""Retrieves all the courses a
user
is enrolled in.
Takes a
student and retrieves all relative enrollments. Includes information regarding how the student
is enrolled
Takes a
user and retrieves all relative enrollments. Includes information regarding how the user
is enrolled
in the the course.
Args:
student_id (str): The username of the student
we want to retrieve course enrollment information for.
user_id (str): The username of the user
we want to retrieve course enrollment information for.
Returns:
A list of enrollment information for the given
student
.
A list of enrollment information for the given
user
.
Examples:
>>> get_enrollments("Bob")
...
...
@@ -58,7 +32,7 @@ def get_enrollments(student_id):
"created": "2014-10-20T20:18:00Z",
"mode": "honor",
"is_active": True,
"
student
": "Bob",
"
user
": "Bob",
"course": {
"course_id": "edX/DemoX/2014T2",
"enrollment_end": 2014-12-20T20:18:00Z,
...
...
@@ -81,7 +55,7 @@ def get_enrollments(student_id):
"created": "2014-10-25T20:18:00Z",
"mode": "verified",
"is_active": True,
"
student
": "Bob",
"
user
": "Bob",
"course": {
"course_id": "edX/edX-Insider/2014T2",
"enrollment_end": 2014-12-20T20:18:00Z,
...
...
@@ -103,16 +77,16 @@ def get_enrollments(student_id):
]
"""
return
_data_api
()
.
get_course_enrollments
(
student
_id
)
return
_data_api
()
.
get_course_enrollments
(
user
_id
)
def
get_enrollment
(
student
_id
,
course_id
):
"""Retrieves all enrollment information for the
student
in respect to a specific course.
def
get_enrollment
(
user
_id
,
course_id
):
"""Retrieves all enrollment information for the
user
in respect to a specific course.
Gets all the course enrollment information specific to a
student
in a course.
Gets all the course enrollment information specific to a
user
in a course.
Args:
student_id (str): The student
to get course enrollment information for.
user_id (str): The user
to get course enrollment information for.
course_id (str): The course to get enrollment information for.
Returns:
...
...
@@ -124,7 +98,7 @@ def get_enrollment(student_id, course_id):
"created": "2014-10-20T20:18:00Z",
"mode": "honor",
"is_active": True,
"
student
": "Bob",
"
user
": "Bob",
"course": {
"course_id": "edX/DemoX/2014T2",
"enrollment_end": 2014-12-20T20:18:00Z,
...
...
@@ -145,17 +119,17 @@ def get_enrollment(student_id, course_id):
}
"""
return
_data_api
()
.
get_course_enrollment
(
student
_id
,
course_id
)
return
_data_api
()
.
get_course_enrollment
(
user
_id
,
course_id
)
def
add_enrollment
(
student
_id
,
course_id
,
mode
=
'honor'
,
is_active
=
True
):
"""Enrolls a
student
in a course.
def
add_enrollment
(
user
_id
,
course_id
,
mode
=
'honor'
,
is_active
=
True
):
"""Enrolls a
user
in a course.
Enrolls a
student
in a course. If the mode is not specified, this will default to 'honor'.
Enrolls a
user
in a course. If the mode is not specified, this will default to 'honor'.
Args:
student_id (str): The student
to enroll.
course_id (str): The course to enroll the
student
in.
user_id (str): The user
to enroll.
course_id (str): The course to enroll the
user
in.
mode (str): Optional argument for the type of enrollment to create. Ex. 'audit', 'honor', 'verified',
'professional'. If not specified, this defaults to 'honor'.
is_active (boolean): Optional argument for making the new enrollment inactive. If not specified, is_active
...
...
@@ -170,7 +144,7 @@ def add_enrollment(student_id, course_id, mode='honor', is_active=True):
"created": "2014-10-20T20:18:00Z",
"mode": "honor",
"is_active": True,
"
student
": "Bob",
"
user
": "Bob",
"course": {
"course_id": "edX/DemoX/2014T2",
"enrollment_end": 2014-12-20T20:18:00Z,
...
...
@@ -191,66 +165,19 @@ def add_enrollment(student_id, course_id, mode='honor', is_active=True):
}
"""
_validate_course_mode
(
course_id
,
mode
)
return
_data_api
()
.
update_course_enrollment
(
student_id
,
course_id
,
mode
=
mode
,
is_active
=
is_active
)
def
deactivate_enrollment
(
student_id
,
course_id
):
"""Un-enrolls a student in a course
Deactivate the enrollment of a student in a course. We will not remove the enrollment data, but simply flag it
as inactive.
Args:
student_id (str): The student associated with the deactivated enrollment.
course_id (str): The course associated with the deactivated enrollment.
Returns:
A serializable dictionary representing the deactivated course enrollment for the student.
Example:
>>> deactivate_enrollment("Bob", "edX/DemoX/2014T2")
{
"created": "2014-10-20T20:18:00Z",
"mode": "honor",
"is_active": False,
"student": "Bob",
"course": {
"course_id": "edX/DemoX/2014T2",
"enrollment_end": 2014-12-20T20:18:00Z,
"course_modes": [
{
"slug": "honor",
"name": "Honor Code Certificate",
"min_price": 0,
"suggested_prices": "",
"currency": "usd",
"expiration_datetime": null,
"description": null
}
],
"enrollment_start": 2014-10-15T20:18:00Z,
"invite_only": False
}
}
"""
# Check to see if there is an enrollment. We do not want to create a deactivated enrollment.
if
not
_data_api
()
.
get_course_enrollment
(
student_id
,
course_id
):
raise
EnrollmentNotFoundError
(
u"No enrollment was found for student {student} in course {course}"
.
format
(
student
=
student_id
,
course
=
course_id
)
)
return
_data_api
()
.
update_course_enrollment
(
student_id
,
course_id
,
is_active
=
False
)
return
_data_api
()
.
create_course_enrollment
(
user_id
,
course_id
,
mode
,
is_active
)
def
update_enrollment
(
student_id
,
course_id
,
mod
e
):
def
update_enrollment
(
user_id
,
course_id
,
mode
=
None
,
is_active
=
Non
e
):
"""Updates the course mode for the enrolled user.
Update a course enrollment for the given
student
and course.
Update a course enrollment for the given
user
and course.
Args:
student_id (str): The student
associated with the updated enrollment.
user_id (str): The user
associated with the updated enrollment.
course_id (str): The course associated with the updated enrollment.
mode (str): The new course mode for this enrollment.
is_active (bool): Sets whether the enrollment is active or not.
Returns:
A serializable dictionary representing the updated enrollment.
...
...
@@ -261,7 +188,7 @@ def update_enrollment(student_id, course_id, mode):
"created": "2014-10-20T20:18:00Z",
"mode": "honor",
"is_active": True,
"
student
": "Bob",
"
user
": "Bob",
"course": {
"course_id": "edX/DemoX/2014T2",
"enrollment_end": 2014-12-20T20:18:00Z,
...
...
@@ -283,7 +210,12 @@ def update_enrollment(student_id, course_id, mode):
"""
_validate_course_mode
(
course_id
,
mode
)
return
_data_api
()
.
update_course_enrollment
(
student_id
,
course_id
,
mode
)
enrollment
=
_data_api
()
.
update_course_enrollment
(
user_id
,
course_id
,
mode
=
mode
,
is_active
=
is_active
)
if
enrollment
is
None
:
msg
=
u"Course Enrollment not found for user {user} in course {course}"
.
format
(
user
=
user_id
,
course
=
course_id
)
log
.
warn
(
msg
)
raise
errors
.
EnrollmentNotFoundError
(
msg
)
return
enrollment
def
get_course_enrollment_details
(
course_id
):
...
...
@@ -354,7 +286,7 @@ def _validate_course_mode(course_id, mode):
available
=
", "
.
join
(
available_modes
)
)
log
.
warn
(
msg
)
raise
CourseModeNotFoundError
(
msg
,
course_enrollment_info
)
raise
errors
.
CourseModeNotFoundError
(
msg
,
course_enrollment_info
)
def
_data_api
():
...
...
@@ -371,4 +303,4 @@ def _data_api():
return
importlib
.
import_module
(
api_path
)
except
(
ImportError
,
ValueError
):
log
.
exception
(
u"Could not load module at '{path}'"
.
format
(
path
=
api_path
))
raise
EnrollmentApiLoadError
(
api_path
)
raise
errors
.
EnrollmentApiLoadError
(
api_path
)
common/djangoapps/enrollment/data.py
View file @
0e58e7cb
...
...
@@ -7,37 +7,40 @@ import logging
from
django.contrib.auth.models
import
User
from
opaque_keys.edx.keys
import
CourseKey
from
xmodule.modulestore.django
import
modulestore
from
enrollment.errors
import
CourseNotFoundError
,
CourseEnrollmentClosedError
,
CourseEnrollmentFullError
,
\
CourseEnrollmentExistsError
,
UserNotFoundError
from
enrollment.serializers
import
CourseEnrollmentSerializer
,
CourseField
from
student.models
import
CourseEnrollment
,
NonExistentCourseError
from
student.models
import
CourseEnrollment
,
NonExistentCourseError
,
CourseEnrollmentException
,
EnrollmentClosedError
,
\
CourseFullError
,
AlreadyEnrolledError
log
=
logging
.
getLogger
(
__name__
)
def
get_course_enrollments
(
student
_id
):
"""Retrieve a list representing all aggregated data for a
student
's course enrollments.
def
get_course_enrollments
(
user
_id
):
"""Retrieve a list representing all aggregated data for a
user
's course enrollments.
Construct a representation of all course enrollment data for a specific
student
.
Construct a representation of all course enrollment data for a specific
user
.
Args:
student_id (str): The name of the student
to retrieve course enrollment information for.
user_id (str): The name of the user
to retrieve course enrollment information for.
Returns:
A serializable list of dictionaries of all aggregated enrollment data for a
student
.
A serializable list of dictionaries of all aggregated enrollment data for a
user
.
"""
qset
=
CourseEnrollment
.
objects
.
filter
(
user__username
=
student
_id
,
is_active
=
True
user__username
=
user
_id
,
is_active
=
True
)
.
order_by
(
'created'
)
return
CourseEnrollmentSerializer
(
qset
)
.
data
# pylint: disable=no-member
def
get_course_enrollment
(
student_id
,
course_id
):
"""Retrieve an object representing all aggregated data for a
student
's course enrollment.
def
get_course_enrollment
(
username
,
course_id
):
"""Retrieve an object representing all aggregated data for a
user
's course enrollment.
Get the course enrollment information for a specific
student
and course.
Get the course enrollment information for a specific
user
and course.
Args:
student_id (str): The name of the student
to retrieve course enrollment information for.
username (str): The name of the user
to retrieve course enrollment information for.
course_id (str): The course to retrieve course enrollment information for.
Returns:
...
...
@@ -47,22 +50,65 @@ def get_course_enrollment(student_id, course_id):
course_key
=
CourseKey
.
from_string
(
course_id
)
try
:
enrollment
=
CourseEnrollment
.
objects
.
get
(
user__username
=
student_id
,
course_id
=
course_key
user__username
=
username
,
course_id
=
course_key
)
return
CourseEnrollmentSerializer
(
enrollment
)
.
data
# pylint: disable=no-member
except
CourseEnrollment
.
DoesNotExist
:
return
None
def
update_course_enrollment
(
student_id
,
course_id
,
mode
=
None
,
is_active
=
None
):
"""Modify a course enrollment for a student.
def
create_course_enrollment
(
username
,
course_id
,
mode
,
is_active
):
"""Create a new course enrollment for the given user.
Creates a new course enrollment for the specified user username.
Args:
username (str): The name of the user to create a new course enrollment for.
course_id (str): The course to create the course enrollment for.
mode (str): (Optional) The mode for the new enrollment.
is_active (boolean): (Optional) Determines if the enrollment is active.
Returns:
A serializable dictionary representing the new course enrollment.
Raises:
CourseNotFoundError
CourseEnrollmentFullError
EnrollmentClosedError
CourseEnrollmentExistsError
"""
course_key
=
CourseKey
.
from_string
(
course_id
)
try
:
user
=
User
.
objects
.
get
(
username
=
username
)
except
User
.
DoesNotExist
:
msg
=
u"Not user with username '{username}' found."
.
format
(
username
=
username
)
log
.
warn
(
msg
)
raise
UserNotFoundError
(
msg
)
try
:
enrollment
=
CourseEnrollment
.
enroll
(
user
,
course_key
,
check_access
=
True
)
return
_update_enrollment
(
enrollment
,
is_active
=
is_active
,
mode
=
mode
)
except
NonExistentCourseError
as
err
:
raise
CourseNotFoundError
(
err
.
message
)
except
EnrollmentClosedError
as
err
:
raise
CourseEnrollmentClosedError
(
err
.
message
)
except
CourseFullError
as
err
:
raise
CourseEnrollmentFullError
(
err
.
message
)
except
AlreadyEnrolledError
as
err
:
raise
CourseEnrollmentExistsError
(
err
.
message
)
def
update_course_enrollment
(
username
,
course_id
,
mode
=
None
,
is_active
=
None
):
"""Modify a course enrollment for a user.
Allows updates to a specific course enrollment.
Args:
student_id (str): The name of the student
to retrieve course enrollment information for.
username (str): The name of the user
to retrieve course enrollment information for.
course_id (str): The course to retrieve course enrollment information for.
mode (str): (Optional)
The mode for the new
enrollment.
mode (str): (Optional)
If specified, modify the mode for this
enrollment.
is_active (boolean): (Optional) Determines if the enrollment is active.
Returns:
...
...
@@ -70,12 +116,22 @@ 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
,
check_access
=
True
)
else
:
enrollment
=
CourseEnrollment
.
objects
.
get
(
user
=
student
,
course_id
=
course_key
)
try
:
user
=
User
.
objects
.
get
(
username
=
username
)
except
User
.
DoesNotExist
:
msg
=
u"Not user with username '{username}' found."
.
format
(
username
=
username
)
log
.
warn
(
msg
)
raise
UserNotFoundError
(
msg
)
try
:
enrollment
=
CourseEnrollment
.
objects
.
get
(
user
=
user
,
course_id
=
course_key
)
return
_update_enrollment
(
enrollment
,
is_active
=
is_active
,
mode
=
mode
)
except
CourseEnrollment
.
DoesNotExist
:
return
None
def
_update_enrollment
(
enrollment
,
is_active
=
None
,
mode
=
None
):
enrollment
.
update_enrollment
(
is_active
=
is_active
,
mode
=
mode
)
enrollment
.
save
()
return
CourseEnrollmentSerializer
(
enrollment
)
.
data
# pylint: disable=no-member
...
...
@@ -92,13 +148,14 @@ def get_course_enrollment_info(course_id):
Returns:
A serializable dictionary representing the course's enrollment information.
Raises:
CourseNotFoundError
"""
course_key
=
CourseKey
.
from_string
(
course_id
)
course
=
modulestore
()
.
get_course
(
course_key
)
if
course
is
None
:
log
.
warning
(
u"Requested enrollment information for unknown course {course}"
.
format
(
course
=
course_id
)
)
raise
NonExistentCourseError
msg
=
u"Requested enrollment information for unknown course {course}"
.
format
(
course
=
course_id
)
log
.
warning
(
msg
)
raise
CourseNotFoundError
(
msg
)
return
CourseField
()
.
to_native
(
course
)
common/djangoapps/enrollment/errors.py
0 → 100644
View file @
0e58e7cb
"""All Error Types pertaining to Enrollment."""
class
CourseEnrollmentError
(
Exception
):
"""Generic Course Enrollment Error.
Describes any error that may occur when reading or updating enrollment information for a user or a course.
"""
def
__init__
(
self
,
msg
,
data
=
None
):
super
(
CourseEnrollmentError
,
self
)
.
__init__
(
msg
)
# Corresponding information to help resolve the error.
self
.
data
=
data
class
CourseNotFoundError
(
CourseEnrollmentError
):
pass
class
UserNotFoundError
(
CourseEnrollmentError
):
pass
class
CourseEnrollmentClosedError
(
CourseEnrollmentError
):
pass
class
CourseEnrollmentFullError
(
CourseEnrollmentError
):
pass
class
CourseEnrollmentExistsError
(
CourseEnrollmentError
):
pass
class
CourseModeNotFoundError
(
CourseEnrollmentError
):
"""The requested course mode could not be found."""
pass
class
EnrollmentNotFoundError
(
CourseEnrollmentError
):
"""The requested enrollment could not be found."""
pass
class
EnrollmentApiLoadError
(
CourseEnrollmentError
):
"""The data API could not be loaded."""
pass
common/djangoapps/enrollment/serializers.py
View file @
0e58e7cb
...
...
@@ -53,8 +53,12 @@ class CourseEnrollmentSerializer(serializers.ModelSerializer):
the Course Descriptor and course modes, to give a complete representation of course enrollment.
"""
course
=
CourseField
()
student
=
serializers
.
SerializerMethodField
(
'get_username'
)
course_details
=
serializers
.
SerializerMethodField
(
'get_course_details'
)
user
=
serializers
.
SerializerMethodField
(
'get_username'
)
def
get_course_details
(
self
,
model
):
field
=
CourseField
()
return
field
.
to_native
(
model
.
course
)
def
get_username
(
self
,
model
):
"""Retrieves the username from the associated model."""
...
...
@@ -62,7 +66,7 @@ class CourseEnrollmentSerializer(serializers.ModelSerializer):
class
Meta
:
# pylint: disable=missing-docstring
model
=
CourseEnrollment
fields
=
(
'created'
,
'mode'
,
'is_active'
,
'course
'
,
'student
'
)
fields
=
(
'created'
,
'mode'
,
'is_active'
,
'course
_details'
,
'user
'
)
lookup_field
=
'username'
...
...
common/djangoapps/enrollment/tests/fake_data_api.py
View file @
0e58e7cb
...
...
@@ -31,14 +31,17 @@ def get_course_enrollment(student_id, course_id):
return
_get_fake_enrollment
(
student_id
,
course_id
)
def
create_course_enrollment
(
student_id
,
course_id
,
mode
=
'honor'
,
is_active
=
True
):
"""Stubbed out Enrollment creation request. """
return
add_enrollment
(
student_id
,
course_id
,
mode
=
mode
,
is_active
=
is_active
)
def
update_course_enrollment
(
student_id
,
course_id
,
mode
=
None
,
is_active
=
None
):
"""Stubbed out Enrollment data request."""
enrollment
=
_get_fake_enrollment
(
student_id
,
course_id
)
if
not
enrollment
:
enrollment
=
add_enrollment
(
student_id
,
course_id
)
if
mode
is
not
None
:
if
enrollment
and
mode
is
not
None
:
enrollment
[
'mode'
]
=
mode
if
is_active
is
not
None
:
if
enrollment
and
is_active
is
not
None
:
enrollment
[
'is_active'
]
=
is_active
return
enrollment
...
...
common/djangoapps/enrollment/tests/test_api.py
View file @
0e58e7cb
...
...
@@ -8,6 +8,7 @@ from django.test import TestCase
from
django.test.utils
import
override_settings
from
django.conf
import
settings
from
enrollment
import
api
from
enrollment.errors
import
EnrollmentApiLoadError
,
EnrollmentNotFoundError
,
CourseModeNotFoundError
from
enrollment.tests
import
fake_data_api
...
...
@@ -51,7 +52,7 @@ class EnrollmentTest(TestCase):
get_result
=
api
.
get_enrollment
(
self
.
USERNAME
,
self
.
COURSE_ID
)
self
.
assertEquals
(
result
,
get_result
)
@raises
(
api
.
CourseModeNotFoundError
)
@raises
(
CourseModeNotFoundError
)
def
test_prof_ed_enroll
(
self
):
# Add a fake course enrollment information to the fake data API
fake_data_api
.
add_course
(
self
.
COURSE_ID
,
course_modes
=
[
'professional'
])
...
...
@@ -83,18 +84,18 @@ class EnrollmentTest(TestCase):
self
.
assertEquals
(
result
[
'mode'
],
mode
)
self
.
assertTrue
(
result
[
'is_active'
])
result
=
api
.
deactivate_enrollment
(
self
.
USERNAME
,
self
.
COURSE_ID
)
result
=
api
.
update_enrollment
(
self
.
USERNAME
,
self
.
COURSE_ID
,
mode
=
mode
,
is_active
=
False
)
self
.
assertIsNotNone
(
result
)
self
.
assertEquals
(
result
[
'student'
],
self
.
USERNAME
)
self
.
assertEquals
(
result
[
'course'
][
'course_id'
],
self
.
COURSE_ID
)
self
.
assertEquals
(
result
[
'mode'
],
mode
)
self
.
assertFalse
(
result
[
'is_active'
])
@raises
(
api
.
EnrollmentNotFoundError
)
@raises
(
EnrollmentNotFoundError
)
def
test_unenroll_not_enrolled_in_course
(
self
):
# Add a fake course enrollment information to the fake data API
fake_data_api
.
add_course
(
self
.
COURSE_ID
,
course_modes
=
[
'honor'
])
api
.
deactivate_enrollment
(
self
.
USERNAME
,
self
.
COURSE_ID
)
api
.
update_enrollment
(
self
.
USERNAME
,
self
.
COURSE_ID
,
mode
=
'honor'
,
is_active
=
False
)
@ddt.data
(
# Simple test of honor and verified.
...
...
@@ -145,7 +146,7 @@ class EnrollmentTest(TestCase):
self
.
assertEquals
(
3
,
len
(
result
[
'course_modes'
]))
@override_settings
(
ENROLLMENT_DATA_API
=
'foo.bar.biz.baz'
)
@raises
(
api
.
EnrollmentApiLoadError
)
@raises
(
EnrollmentApiLoadError
)
def
test_data_api_config_error
(
self
):
# Enroll in the course and verify the URL we get sent to
api
.
add_enrollment
(
self
.
USERNAME
,
self
.
COURSE_ID
,
mode
=
'audit'
)
common/djangoapps/enrollment/tests/test_data.py
View file @
0e58e7cb
...
...
@@ -3,6 +3,7 @@ Test the Data Aggregation Layer for Course Enrollments.
"""
import
ddt
from
mock
import
patch
from
nose.tools
import
raises
import
unittest
...
...
@@ -12,8 +13,10 @@ from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase
,
mixed_store_config
)
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
enrollment.errors
import
CourseNotFoundError
,
UserNotFoundError
,
CourseEnrollmentClosedError
,
\
CourseEnrollmentFullError
,
CourseEnrollmentExistsError
from
student.tests.factories
import
UserFactory
,
CourseModeFactory
from
student.models
import
CourseEnrollment
,
NonExistentCourse
Error
from
student.models
import
CourseEnrollment
,
EnrollmentClosedError
,
CourseFullError
,
AlreadyEnrolled
Error
from
enrollment
import
data
# Since we don't need any XML course fixtures, use a modulestore configuration
...
...
@@ -54,12 +57,11 @@ class EnrollmentDataTest(ModuleStoreTestCase):
def
test_enroll
(
self
,
course_modes
,
enrollment_mode
):
# Create the course modes (if any) required for this test case
self
.
_create_course_modes
(
course_modes
)
enrollment
=
data
.
update_course_enrollment
(
enrollment
=
data
.
create_course_enrollment
(
self
.
user
.
username
,
unicode
(
self
.
course
.
id
),
mode
=
enrollment_mode
,
is_active
=
True
enrollment_mode
,
True
)
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
self
.
user
,
self
.
course
.
id
))
...
...
@@ -72,7 +74,7 @@ class EnrollmentDataTest(ModuleStoreTestCase):
self
.
assertEqual
(
is_active
,
enrollment
[
'is_active'
])
def
test_unenroll
(
self
):
# Enroll the
student
in the course
# Enroll the
user
in the course
CourseEnrollment
.
enroll
(
self
.
user
,
self
.
course
.
id
,
mode
=
"honor"
)
enrollment
=
data
.
update_course_enrollment
(
...
...
@@ -119,9 +121,11 @@ class EnrollmentDataTest(ModuleStoreTestCase):
for
course
in
created_courses
:
self
.
_create_course_modes
(
course_modes
,
course
=
course
)
# Create the original enrollment.
created_enrollments
.
append
(
data
.
upd
ate_course_enrollment
(
created_enrollments
.
append
(
data
.
cre
ate_course_enrollment
(
self
.
user
.
username
,
unicode
(
course
.
id
),
'honor'
,
True
))
# Compare the created enrollments with the results
...
...
@@ -148,18 +152,18 @@ class EnrollmentDataTest(ModuleStoreTestCase):
self
.
assertIsNone
(
result
)
# Create the original enrollment.
enrollment
=
data
.
upd
ate_course_enrollment
(
enrollment
=
data
.
cre
ate_course_enrollment
(
self
.
user
.
username
,
unicode
(
self
.
course
.
id
),
mode
=
enrollment_mode
,
is_active
=
True
enrollment_mode
,
True
)
# Get the enrollment and compare it to the original.
result
=
data
.
get_course_enrollment
(
self
.
user
.
username
,
unicode
(
self
.
course
.
id
))
self
.
assertEqual
(
self
.
user
.
username
,
result
[
'
student
'
])
self
.
assertEqual
(
self
.
user
.
username
,
result
[
'
user
'
])
self
.
assertEqual
(
enrollment
,
result
)
@raises
(
NonExistentCourse
Error
)
@raises
(
CourseNotFound
Error
)
def
test_non_existent_course
(
self
):
data
.
get_course_enrollment_info
(
"this/is/bananas"
)
...
...
@@ -172,3 +176,37 @@ class EnrollmentDataTest(ModuleStoreTestCase):
mode_slug
=
mode_slug
,
mode_display_name
=
mode_slug
,
)
@raises
(
UserNotFoundError
)
def
test_enrollment_for_non_existent_user
(
self
):
data
.
create_course_enrollment
(
"some_fake_user"
,
unicode
(
self
.
course
.
id
),
'honor'
,
True
)
@raises
(
CourseNotFoundError
)
def
test_enrollment_for_non_existent_course
(
self
):
data
.
create_course_enrollment
(
self
.
user
.
username
,
"some/fake/course"
,
'honor'
,
True
)
@raises
(
CourseEnrollmentClosedError
)
@patch.object
(
CourseEnrollment
,
"enroll"
)
def
test_enrollment_for_closed_course
(
self
,
mock_enroll
):
mock_enroll
.
side_effect
=
EnrollmentClosedError
(
"Bad things happened"
)
data
.
create_course_enrollment
(
self
.
user
.
username
,
unicode
(
self
.
course
.
id
),
'honor'
,
True
)
@raises
(
CourseEnrollmentFullError
)
@patch.object
(
CourseEnrollment
,
"enroll"
)
def
test_enrollment_for_closed_course
(
self
,
mock_enroll
):
mock_enroll
.
side_effect
=
CourseFullError
(
"Bad things happened"
)
data
.
create_course_enrollment
(
self
.
user
.
username
,
unicode
(
self
.
course
.
id
),
'honor'
,
True
)
@raises
(
CourseEnrollmentExistsError
)
@patch.object
(
CourseEnrollment
,
"enroll"
)
def
test_enrollment_for_closed_course
(
self
,
mock_enroll
):
mock_enroll
.
side_effect
=
AlreadyEnrolledError
(
"Bad things happened"
)
data
.
create_course_enrollment
(
self
.
user
.
username
,
unicode
(
self
.
course
.
id
),
'honor'
,
True
)
@raises
(
UserNotFoundError
)
def
test_update_for_non_existent_user
(
self
):
data
.
update_course_enrollment
(
"some_fake_user"
,
unicode
(
self
.
course
.
id
),
is_active
=
False
)
def
test_update_for_non_existent_course
(
self
):
enrollment
=
data
.
update_course_enrollment
(
self
.
user
.
username
,
"some/fake/course"
,
is_active
=
False
)
self
.
assertIsNone
(
enrollment
)
common/djangoapps/enrollment/tests/test_views.py
View file @
0e58e7cb
"""
Tests for
student
enrollment.
Tests for
user
enrollment.
"""
import
ddt
import
json
import
unittest
from
mock
import
patch
from
django.test.utils
import
override_settings
from
django.core.urlresolvers
import
reverse
from
rest_framework.test
import
APITestCase
...
...
@@ -14,6 +15,8 @@ from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase
,
mixed_store_config
)
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
enrollment
import
api
from
enrollment.errors
import
CourseEnrollmentError
from
student.tests.factories
import
UserFactory
,
CourseModeFactory
from
student.models
import
CourseEnrollment
...
...
@@ -27,7 +30,7 @@ MODULESTORE_CONFIG = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {}, incl
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
class
EnrollmentTest
(
ModuleStoreTestCase
,
APITestCase
):
"""
Test
student
enrollment, especially with different course modes.
Test
user
enrollment, especially with different course modes.
"""
USERNAME
=
"Bob"
EMAIL
=
"bob@example.com"
...
...
@@ -68,6 +71,23 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
self
.
assertTrue
(
is_active
)
self
.
assertEqual
(
course_mode
,
enrollment_mode
)
def
test_check_enrollment
(
self
):
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
'honor'
,
mode_display_name
=
'Honor'
,
)
# Create an enrollment
self
.
_create_enrollment
()
resp
=
self
.
client
.
get
(
reverse
(
'courseenrollment'
,
kwargs
=
{
"user"
:
self
.
user
.
username
,
"course_id"
:
unicode
(
self
.
course
.
id
)})
)
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_200_OK
)
data
=
json
.
loads
(
resp
.
content
)
self
.
assertEqual
(
unicode
(
self
.
course
.
id
),
data
[
'course_details'
][
'course_id'
])
self
.
assertEqual
(
'honor'
,
data
[
'mode'
])
self
.
assertTrue
(
data
[
'is_active'
])
def
test_enroll_prof_ed
(
self
):
# Create the prod ed mode.
CourseModeFactory
.
create
(
...
...
@@ -77,51 +97,125 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
)
# Enroll in the course, this will fail if the mode is not explicitly professional.
resp
=
self
.
client
.
post
(
reverse
(
'courseenrollment'
,
kwargs
=
{
'course_id'
:
(
unicode
(
self
.
course
.
id
))}))
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_400_BAD_REQUEST
)
resp
=
self
.
_create_enrollment
(
expected_status
=
status
.
HTTP_400_BAD_REQUEST
)
# While the enrollment wrong is invalid, the response content should have
# all the valid enrollment modes.
data
=
json
.
loads
(
resp
.
content
)
self
.
assertEqual
(
unicode
(
self
.
course
.
id
),
data
[
'course_id'
])
self
.
assertEqual
(
1
,
len
(
data
[
'course_modes'
]))
self
.
assertEqual
(
'professional'
,
data
[
'course_modes'
][
0
][
'slug'
])
self
.
assertEqual
(
unicode
(
self
.
course
.
id
),
data
[
'course_
details'
][
'course_
id'
])
self
.
assertEqual
(
1
,
len
(
data
[
'course_
details'
][
'course_
modes'
]))
self
.
assertEqual
(
'professional'
,
data
[
'course_
details'
][
'course_
modes'
][
0
][
'slug'
])
def
test_user_not_authenticated
(
self
):
# Log out, so we're no longer authenticated
self
.
client
.
logout
()
# Try to enroll, this should fail.
resp
=
self
.
client
.
post
(
reverse
(
'courseenrollment'
,
kwargs
=
{
'course_id'
:
(
unicode
(
self
.
course
.
id
))}))
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_403_FORBIDDEN
)
self
.
_create_enrollment
(
expected_status
=
status
.
HTTP_403_FORBIDDEN
)
def
test_user_not_activated
(
self
):
# Create a user account, but don't activate it
# Log out the default user, Bob.
self
.
client
.
logout
()
# Create a user account
self
.
user
=
UserFactory
.
create
(
username
=
"inactive"
,
email
=
"inactive@example.com"
,
password
=
self
.
PASSWORD
,
is_active
=
Fals
e
is_active
=
Tru
e
)
# Log in with the unactivated account
self
.
client
.
login
(
username
=
"inactive"
,
password
=
self
.
PASSWORD
)
# Deactivate the user. Has to be done after login to get the user into the
# request and properly logged in.
self
.
user
.
is_active
=
False
self
.
user
.
save
()
# Enrollment should succeed, even though we haven't authenticated.
resp
=
self
.
client
.
post
(
reverse
(
'courseenrollment'
,
kwargs
=
{
'course_id'
:
(
unicode
(
self
.
course
.
id
))}))
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
_create_enrollment
()
def
test_user_does_not_match_url
(
self
):
# Try to enroll a user that is not the authenticated user.
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
'honor'
,
mode_display_name
=
'Honor'
,
)
self
.
_create_enrollment
(
username
=
'not_the_user'
,
expected_status
=
status
.
HTTP_404_NOT_FOUND
)
def
test_user_does_not_match_param_for_list
(
self
):
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
'honor'
,
mode_display_name
=
'Honor'
,
)
resp
=
self
.
client
.
get
(
reverse
(
'courseenrollments'
),
{
"user"
:
"not_the_user"
})
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_404_NOT_FOUND
)
def
test_user_does_not_match_param
(
self
):
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
'honor'
,
mode_display_name
=
'Honor'
,
)
resp
=
self
.
client
.
get
(
reverse
(
'courseenrollment'
,
kwargs
=
{
"user"
:
"not_the_user"
,
"course_id"
:
unicode
(
self
.
course
.
id
)})
)
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_404_NOT_FOUND
)
def
test_get_course_details
(
self
):
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
'honor'
,
mode_display_name
=
'Honor'
,
)
resp
=
self
.
client
.
get
(
reverse
(
'courseenrollmentdetails'
,
kwargs
=
{
"course_id"
:
unicode
(
self
.
course
.
id
)})
)
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_200_OK
)
data
=
json
.
loads
(
resp
.
content
)
self
.
assertEqual
(
unicode
(
self
.
course
.
id
),
data
[
'course_id'
])
def
test_with_invalid_course_id
(
self
):
# Create an enrollment
resp
=
self
.
client
.
post
(
reverse
(
'courseenrollment'
,
kwargs
=
{
'course_id'
:
'entirely/fake/course'
}))
self
.
_create_enrollment
(
course_id
=
'entirely/fake/course'
,
expected_status
=
status
.
HTTP_400_BAD_REQUEST
)
def
test_get_enrollment_details_bad_course
(
self
):
resp
=
self
.
client
.
get
(
reverse
(
'courseenrollmentdetails'
,
kwargs
=
{
"course_id"
:
"some/fake/course"
})
)
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_400_BAD_REQUEST
)
def
_create_enrollment
(
self
):
@patch.object
(
api
,
"get_enrollment"
)
def
test_get_enrollment_internal_error
(
self
,
mock_get_enrollment
):
mock_get_enrollment
.
side_effect
=
CourseEnrollmentError
(
"Something bad happened."
)
resp
=
self
.
client
.
get
(
reverse
(
'courseenrollment'
,
kwargs
=
{
"user"
:
self
.
user
.
username
,
"course_id"
:
unicode
(
self
.
course
.
id
)})
)
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_400_BAD_REQUEST
)
def
_create_enrollment
(
self
,
course_id
=
None
,
username
=
None
,
expected_status
=
status
.
HTTP_200_OK
):
course_id
=
unicode
(
self
.
course
.
id
)
if
course_id
is
None
else
course_id
username
=
self
.
user
.
username
if
username
is
None
else
username
"""Enroll in the course and verify the URL we are sent to. """
resp
=
self
.
client
.
post
(
reverse
(
'courseenrollment'
,
kwargs
=
{
'course_id'
:
(
unicode
(
self
.
course
.
id
))}))
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_200_OK
)
data
=
json
.
loads
(
resp
.
content
)
self
.
assertEqual
(
unicode
(
self
.
course
.
id
),
data
[
'course'
][
'course_id'
])
self
.
assertEqual
(
'honor'
,
data
[
'mode'
])
self
.
assertTrue
(
data
[
'is_active'
])
resp
=
self
.
client
.
post
(
reverse
(
'courseenrollments'
),
{
'course_details'
:
{
'course_id'
:
course_id
},
'user'
:
username
},
format
=
'json'
)
self
.
assertEqual
(
resp
.
status_code
,
expected_status
)
if
expected_status
==
status
.
HTTP_200_OK
:
data
=
json
.
loads
(
resp
.
content
)
self
.
assertEqual
(
course_id
,
data
[
'course_details'
][
'course_id'
])
self
.
assertEqual
(
'honor'
,
data
[
'mode'
])
self
.
assertTrue
(
data
[
'is_active'
])
return
resp
common/djangoapps/enrollment/urls.py
View file @
0e58e7cb
...
...
@@ -5,17 +5,25 @@ URLs for the Enrollment API
from
django.conf
import
settings
from
django.conf.urls
import
patterns
,
url
from
.views
import
get_course_enrollment
,
list_student_enrollments
from
.views
import
(
EnrollmentView
,
EnrollmentListView
,
EnrollmentCourseDetailView
)
urlpatterns
=
[]
USER_PATTERN
=
'(?P<user>[
\
w.@+-]+)'
if
settings
.
FEATURES
.
get
(
'ENABLE_COMBINED_LOGIN_REGISTRATION'
):
urlpatterns
+=
patterns
(
'enrollment.views'
,
url
(
r'^student$'
,
list_student_enrollments
,
name
=
'courseenrollments'
),
url
(
r'^course/{course_key}$'
.
format
(
course_key
=
settings
.
COURSE_ID_PATTERN
),
get_course_enrollment
,
name
=
'courseenrollment'
),
)
urlpatterns
=
patterns
(
'enrollment.views'
,
url
(
r'^enrollment/{user},{course_key}$'
.
format
(
user
=
USER_PATTERN
,
course_key
=
settings
.
COURSE_ID_PATTERN
),
EnrollmentView
.
as_view
(),
name
=
'courseenrollment'
),
url
(
r'^enrollment$'
,
EnrollmentListView
.
as_view
(),
name
=
'courseenrollments'
),
url
(
r'^course/{course_key}$'
.
format
(
course_key
=
settings
.
COURSE_ID_PATTERN
),
EnrollmentCourseDetailView
.
as_view
(),
name
=
'courseenrollmentdetails'
),
)
common/djangoapps/enrollment/views.py
View file @
0e58e7cb
...
...
@@ -4,13 +4,13 @@ consist primarily of authentication, request validation, and serialization.
"""
from
rest_framework
import
status
from
rest_framework.authentication
import
OAuth2Authentication
,
SessionAuthentication
from
rest_framework.decorators
import
api_view
,
authentication_classes
,
permission_classes
,
throttle_classes
from
rest_framework.permissions
import
IsAuthenticated
from
rest_framework.authentication
import
OAuth2Authentication
from
rest_framework
import
permissions
from
rest_framework.response
import
Response
from
rest_framework.throttling
import
UserRateThrottle
from
rest_framework.views
import
APIView
from
enrollment
import
api
from
student.models
import
NonExistentCourseError
,
CourseEnrollmentException
from
enrollment.errors
import
CourseNotFoundError
,
CourseEnrollmentError
,
CourseModeNotFoundError
from
util.authentication
import
SessionAuthenticationAllowInactiveUser
...
...
@@ -20,58 +20,181 @@ class EnrollmentUserThrottle(UserRateThrottle):
rate
=
'50/second'
@api_view
([
'GET'
])
@authentication_classes
((
OAuth2Authentication
,
SessionAuthentication
))
@permission_classes
((
IsAuthenticated
,))
@throttle_classes
([
EnrollmentUserThrottle
])
def
list_student_enrollments
(
request
):
"""List out all the enrollments for the current student
Returns a JSON response with all the course enrollments for the current student.
Args:
request (Request): The GET request for course enrollment listings.
Returns:
A JSON serialized representation of the student's course enrollments.
"""
return
Response
(
api
.
get_enrollments
(
request
.
user
.
username
))
@api_view
([
'GET'
,
'POST'
])
@authentication_classes
((
OAuth2Authentication
,
SessionAuthenticationAllowInactiveUser
))
@permission_classes
((
IsAuthenticated
,))
@throttle_classes
([
EnrollmentUserThrottle
])
def
get_course_enrollment
(
request
,
course_id
=
None
):
"""Create, read, or update enrollment information for a student.
HTTP Endpoint for all CRUD operations for a student course enrollment. Allows creation, reading, and
updates of the current enrollment for a particular course.
Args:
request (Request): To get current course enrollment information, a GET request will return
information for the current user and the specified course. A POST request will create a
new course enrollment for the current user. If 'mode' or 'deactivate' are found in the
POST parameters, the mode can be modified, or the enrollment can be deactivated.
course_id (str): URI element specifying the course location. Enrollment information will be
returned, created, or updated for this particular course.
Return:
A JSON serialized representation of the course enrollment. If this is a new or modified enrollment,
the returned enrollment will reflect all changes.
"""
try
:
if
course_id
and
request
.
method
==
'POST'
:
return
Response
(
api
.
add_enrollment
(
request
.
user
.
username
,
course_id
))
else
:
return
Response
(
api
.
get_enrollment
(
request
.
user
.
username
,
course_id
))
except
api
.
CourseModeNotFoundError
as
error
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
error
.
data
)
except
NonExistentCourseError
:
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
)
class
EnrollmentView
(
APIView
):
""" Enrollment API View for creating, updating, and viewing course enrollments. """
authentication_classes
=
OAuth2Authentication
,
SessionAuthenticationAllowInactiveUser
permission_classes
=
permissions
.
IsAuthenticated
,
throttle_classes
=
EnrollmentUserThrottle
,
def
get
(
self
,
request
,
course_id
=
None
,
user
=
None
):
"""Create, read, or update enrollment information for a user.
HTTP Endpoint for all CRUD operations for a user course enrollment. Allows creation, reading, and
updates of the current enrollment for a particular course.
Args:
request (Request): To get current course enrollment information, a GET request will return
information for the current user and the specified course.
course_id (str): URI element specifying the course location. Enrollment information will be
returned, created, or updated for this particular course.
user (str): The user username associated with this enrollment request.
Return:
A JSON serialized representation of the course enrollment.
"""
if
request
.
user
.
username
!=
user
:
# Return a 404 instead of a 403 (Unauthorized). If one user is looking up
# other users, do not let them deduce the existence of an enrollment.
return
Response
(
status
=
status
.
HTTP_404_NOT_FOUND
)
try
:
return
Response
(
api
.
get_enrollment
(
user
,
course_id
))
except
CourseEnrollmentError
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"message"
:
(
u"An error occurred while retrieving enrollments for user "
u"'{user}' in course '{course_id}'"
)
.
format
(
user
=
user
,
course_id
=
course_id
)
}
)
class
EnrollmentCourseDetailView
(
APIView
):
""" Enrollment API View for viewing course enrollment details. """
authentication_classes
=
[]
permission_classes
=
[]
throttle_classes
=
EnrollmentUserThrottle
,
def
get
(
self
,
request
,
course_id
=
None
):
"""Read enrollment information for a particular course.
HTTP Endpoint for retrieving course level enrollment information.
Args:
request (Request): To get current course enrollment information, a GET request will return
information for the specified course.
course_id (str): URI element specifying the course location. Enrollment information will be
returned.
Return:
A JSON serialized representation of the course enrollment details.
"""
try
:
return
Response
(
api
.
get_course_enrollment_details
(
course_id
))
except
CourseNotFoundError
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"message"
:
(
u"No course found for course ID '{course_id}'"
)
.
format
(
course_id
=
course_id
)
}
)
class
EnrollmentListView
(
APIView
):
""" Enrollment API List View for viewing all course enrollments for a user. """
authentication_classes
=
OAuth2Authentication
,
SessionAuthenticationAllowInactiveUser
permission_classes
=
permissions
.
IsAuthenticated
,
throttle_classes
=
EnrollmentUserThrottle
,
def
get
(
self
,
request
):
"""List out all the enrollments for the current user
Returns a JSON response with all the course enrollments for the current user.
Args:
request (Request): The GET request for course enrollment listings.
user (str): Get all enrollments for the specified user's username.
Returns:
A JSON serialized representation of the user's course enrollments.
"""
user
=
request
.
GET
.
get
(
'user'
,
request
.
user
.
username
)
if
request
.
user
.
username
!=
user
:
# Return a 404 instead of a 403 (Unauthorized). If one user is looking up
# other users, do not let them deduce the existence of an enrollment.
return
Response
(
status
=
status
.
HTTP_404_NOT_FOUND
)
try
:
return
Response
(
api
.
get_enrollments
(
user
))
except
CourseEnrollmentError
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"message"
:
(
u"An error occurred while retrieving enrollments for user '{user}'"
)
.
format
(
user
=
user
)
}
)
def
post
(
self
,
request
):
"""Create a new enrollment
Creates a new enrollment based on the data posted. Currently all that can be specified is
the course id. All other attributes will be determined by the server, and cannot be updated
through this endpoint.
By default, this will attempt to create the enrollment with course mode 'honor'. If the course
does not have an 'honor' course mode, it will fail as a bad request and list the available
course modes.
Args:
request (Request): The POST request to create a new enrollment. POST DATA should contain
'course_details' with an attribute 'course_id' to identify which course to enroll in.
'user' may be specified as well, but must match the username of the authenticated user.
Ex. {'user': 'Bob', 'course_details': { 'course_id': 'edx/demo/T2014' } }
Returns:
A JSON serialized representation of the user's new course enrollment.
"""
user
=
request
.
DATA
.
get
(
'user'
,
request
.
user
.
username
)
if
not
user
:
user
=
request
.
user
.
username
if
user
!=
request
.
user
.
username
:
# Return a 404 instead of a 403 (Unauthorized). If one user is looking up
# other users, do not let them deduce the existence of an enrollment.
return
Response
(
status
=
status
.
HTTP_404_NOT_FOUND
)
if
'course_details'
not
in
request
.
DATA
or
'course_id'
not
in
request
.
DATA
[
'course_details'
]:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"message"
:
u"Course ID must be specified to create a new enrollment."
}
)
course_id
=
request
.
DATA
[
'course_details'
][
'course_id'
]
try
:
return
Response
(
api
.
add_enrollment
(
user
,
course_id
))
except
CourseModeNotFoundError
as
error
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"message"
:
(
u"The course mode '{mode}' is not available for course '{course_id}'."
)
.
format
(
mode
=
"honor"
,
course_id
=
course_id
),
"course_details"
:
error
.
data
})
except
CourseNotFoundError
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"message"
:
u"No course '{course_id}' found for enrollment"
.
format
(
course_id
=
course_id
)
}
)
except
CourseEnrollmentError
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"message"
:
(
u"An error occurred while creating the new course enrollment for user "
u"'{user}' in course '{course_id}'"
)
.
format
(
user
=
user
,
course_id
=
course_id
)
}
)
lms/static/js/spec/student_account/enrollment_spec.js
View file @
0e58e7cb
...
...
@@ -5,7 +5,7 @@ define(['js/common_helpers/ajax_helpers', 'js/student_account/enrollment'],
describe
(
'edx.student.account.EnrollmentInterface'
,
function
()
{
var
COURSE_KEY
=
'edX/DemoX/Fall'
,
ENROLL_URL
=
'/
enrollment/v0/course/edX/DemoX/Fall
'
,
ENROLL_URL
=
'/
api/enrollment/v1/enrollment
'
,
FORWARD_URL
=
'/course_modes/choose/edX/DemoX/Fall/'
;
beforeEach
(
function
()
{
...
...
@@ -21,7 +21,12 @@ define(['js/common_helpers/ajax_helpers', 'js/student_account/enrollment'],
EnrollmentInterface
.
enroll
(
COURSE_KEY
);
// Expect that the correct request was made to the server
AjaxHelpers
.
expectRequest
(
requests
,
'POST'
,
ENROLL_URL
);
AjaxHelpers
.
expectRequest
(
requests
,
'POST'
,
ENROLL_URL
,
'{"course_details":{"course_id":"edX/DemoX/Fall"}}'
);
// Simulate a successful response from the server
AjaxHelpers
.
respondWithJson
(
requests
,
{});
...
...
lms/static/js/student_account/enrollment.js
View file @
0e58e7cb
...
...
@@ -9,7 +9,7 @@ var edx = edx || {};
edx
.
student
.
account
.
EnrollmentInterface
=
{
urls
:
{
course
:
'/enrollment/v0/course/
'
,
enrollment
:
'/api/enrollment/v1/enrollment
'
,
trackSelection
:
'/course_modes/choose/'
},
...
...
@@ -23,10 +23,17 @@ var edx = edx || {};
* @param {string} courseKey Slash-separated course key.
*/
enroll
:
function
(
courseKey
)
{
var
data_obj
=
{
course_details
:
{
course_id
:
courseKey
}
};
var
data
=
JSON
.
stringify
(
data_obj
);
$
.
ajax
({
url
:
this
.
courseEnrollmentUrl
(
courseKey
)
,
url
:
this
.
urls
.
enrollment
,
type
:
'POST'
,
data
:
{},
contentType
:
'application/json; charset=utf-8'
,
data
:
data
,
headers
:
this
.
headers
,
context
:
this
}).
always
(
function
()
{
...
...
@@ -44,15 +51,6 @@ var edx = edx || {};
},
/**
* Construct a URL to enroll in a course.
* @param {string} courseKey Slash-separated course key.
* @return {string} The URL to enroll in a course.
*/
courseEnrollmentUrl
:
function
(
courseKey
)
{
return
this
.
urls
.
course
+
courseKey
;
},
/**
* Redirect to a URL. Mainly useful for mocking out in tests.
* @param {string} url The URL to redirect to.
*/
...
...
lms/urls.py
View file @
0e58e7cb
...
...
@@ -74,7 +74,7 @@ urlpatterns = ('', # nopep8
url
(
r'^submit_feedback$'
,
'util.views.submit_feedback'
),
# Enrollment API RESTful endpoints
url
(
r'^
enrollment/v0
/'
,
include
(
'enrollment.urls'
)),
url
(
r'^
api/enrollment/v1
/'
,
include
(
'enrollment.urls'
)),
)
...
...
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