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
d9e47a6b
Commit
d9e47a6b
authored
Oct 23, 2014
by
Stephen Sanchez
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #5695 from edx/sanchez/enrollment-tests-and-cleanup
enrollment tests and cleanup
parents
aba600cf
e7570c52
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
716 additions
and
18 deletions
+716
-18
common/djangoapps/enrollment/api.py
+0
-0
common/djangoapps/enrollment/data.py
+62
-5
common/djangoapps/enrollment/serializers.py
+37
-5
common/djangoapps/enrollment/tests/fake_data_api.py
+96
-0
common/djangoapps/enrollment/tests/test_api.py
+151
-0
common/djangoapps/enrollment/tests/test_data.py
+172
-0
common/djangoapps/enrollment/tests/test_views.py
+152
-0
common/djangoapps/enrollment/views.py
+46
-8
No files found.
common/djangoapps/enrollment/api.py
View file @
d9e47a6b
This diff is collapsed.
Click to expand it.
common/djangoapps/enrollment/data.py
View file @
d9e47a6b
...
@@ -3,13 +3,29 @@ Data Aggregation Layer of the Enrollment API. Collects all enrollment specific d
...
@@ -3,13 +3,29 @@ Data Aggregation Layer of the Enrollment API. Collects all enrollment specific d
source to be used throughout the API.
source to be used throughout the API.
"""
"""
import
logging
from
django.contrib.auth.models
import
User
from
django.contrib.auth.models
import
User
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
enrollment.serializers
import
CourseEnrollmentSerializer
from
xmodule.modulestore.django
import
modulestore
from
student.models
import
CourseEnrollment
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
enrollment.serializers
import
CourseEnrollmentSerializer
,
CourseField
from
student.models
import
CourseEnrollment
,
NonExistentCourseError
log
=
logging
.
getLogger
(
__name__
)
def
get_course_enrollments
(
student_id
):
def
get_course_enrollments
(
student_id
):
"""Retrieve a list representing all aggregated data for a student's course enrollments.
Construct a representation of all course enrollment data for a specific student..
Args:
student_id (str): The name of the student to retrieve course enrollment information for.
Returns:
A serializable list of dictionaries of all aggregated enrollment data for a student.
"""
qset
=
CourseEnrollment
.
objects
.
filter
(
qset
=
CourseEnrollment
.
objects
.
filter
(
user__username
=
student_id
,
is_active
=
True
user__username
=
student_id
,
is_active
=
True
)
.
order_by
(
'created'
)
)
.
order_by
(
'created'
)
...
@@ -17,6 +33,18 @@ def get_course_enrollments(student_id):
...
@@ -17,6 +33,18 @@ def get_course_enrollments(student_id):
def
get_course_enrollment
(
student_id
,
course_id
):
def
get_course_enrollment
(
student_id
,
course_id
):
"""Retrieve an object representing all aggregated data for a student's course enrollment.
Get the course enrollment information for a specific student and course.
Args:
student_id (str): The name of the student to retrieve course enrollment information for.
course_id (str): The course to retrieve course enrollment information for.
Returns:
A serializable dictionary representing the course enrollment.
"""
course_key
=
CourseKey
.
from_string
(
course_id
)
course_key
=
CourseKey
.
from_string
(
course_id
)
try
:
try
:
enrollment
=
CourseEnrollment
.
objects
.
get
(
enrollment
=
CourseEnrollment
.
objects
.
get
(
...
@@ -28,6 +56,20 @@ def get_course_enrollment(student_id, course_id):
...
@@ -28,6 +56,20 @@ def get_course_enrollment(student_id, course_id):
def
update_course_enrollment
(
student_id
,
course_id
,
mode
=
None
,
is_active
=
None
):
def
update_course_enrollment
(
student_id
,
course_id
,
mode
=
None
,
is_active
=
None
):
"""Modify a course enrollment for a student.
Allows updates to a specific course enrollment.
Args:
student_id (str): The name of the student 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.
is_active (boolean): (Optional) Determines if the enrollment is active.
Returns:
A serializable dictionary representing the modified course enrollment.
"""
course_key
=
CourseKey
.
from_string
(
course_id
)
course_key
=
CourseKey
.
from_string
(
course_id
)
student
=
User
.
objects
.
get
(
username
=
student_id
)
student
=
User
.
objects
.
get
(
username
=
student_id
)
if
not
CourseEnrollment
.
is_enrolled
(
student
,
course_key
):
if
not
CourseEnrollment
.
is_enrolled
(
student
,
course_key
):
...
@@ -41,8 +83,23 @@ def update_course_enrollment(student_id, course_id, mode=None, is_active=None):
...
@@ -41,8 +83,23 @@ def update_course_enrollment(student_id, course_id, mode=None, is_active=None):
def
get_course_enrollment_info
(
course_id
):
def
get_course_enrollment_info
(
course_id
):
pass
"""Returns all course enrollment information for the given course.
Based on the course id, return all related course information..
def
get_course_enrollments_info
(
student_id
):
Args:
pass
course_id (str): The course to retrieve enrollment information for.
Returns:
A serializable dictionary representing the course's enrollment information.
"""
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
return
CourseField
()
.
to_native
(
course
)
common/djangoapps/enrollment/serializers.py
View file @
d9e47a6b
...
@@ -3,12 +3,36 @@ Serializers for all Course Enrollment related return objects.
...
@@ -3,12 +3,36 @@ Serializers for all Course Enrollment related return objects.
"""
"""
from
rest_framework
import
serializers
from
rest_framework
import
serializers
from
rest_framework.fields
import
Field
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
from
course_modes.models
import
CourseMode
from
course_modes.models
import
CourseMode
class
StringListField
(
serializers
.
CharField
):
"""Custom Serializer for turning a comma delimited string into a list.
This field is designed to take a string such as "1,2,3" and turn it into an actual list
[1,2,3]
"""
def
field_to_native
(
self
,
obj
,
field_name
):
"""
Serialize the object's class name.
"""
if
not
obj
.
suggested_prices
:
return
[]
items
=
obj
.
suggested_prices
.
split
(
','
)
return
[
int
(
item
)
for
item
in
items
]
class
CourseField
(
serializers
.
RelatedField
):
class
CourseField
(
serializers
.
RelatedField
):
"""Custom field to wrap a CourseDescriptor object. Read-only."""
"""Read-Only representation of course enrollment information.
Aggregates course information from the CourseDescriptor as well as the Course Modes configured
for enrolling in the course.
"""
def
to_native
(
self
,
course
):
def
to_native
(
self
,
course
):
course_id
=
unicode
(
course
.
id
)
course_id
=
unicode
(
course
.
id
)
...
@@ -24,8 +48,10 @@ class CourseField(serializers.RelatedField):
...
@@ -24,8 +48,10 @@ class CourseField(serializers.RelatedField):
class
CourseEnrollmentSerializer
(
serializers
.
ModelSerializer
):
class
CourseEnrollmentSerializer
(
serializers
.
ModelSerializer
):
"""
"""Serializes CourseEnrollment models
Serializes CourseEnrollment models
Aggregates all data from the Course Enrollment table, and pulls in the serialization for
the Course Descriptor and course modes, to give a complete representation of course enrollment.
"""
"""
course
=
CourseField
()
course
=
CourseField
()
...
@@ -37,11 +63,17 @@ class CourseEnrollmentSerializer(serializers.ModelSerializer):
...
@@ -37,11 +63,17 @@ class CourseEnrollmentSerializer(serializers.ModelSerializer):
class
ModeSerializer
(
serializers
.
Serializer
):
class
ModeSerializer
(
serializers
.
Serializer
):
"""Serializes a course's 'Mode' tuples"""
"""Serializes a course's 'Mode' tuples
Returns a serialized representation of the modes available for course enrollment. The course
modes models are designed to return a tuple instead of the model object itself. This serializer
does not handle the model object itself, but the tuple.
"""
slug
=
serializers
.
CharField
(
max_length
=
100
)
slug
=
serializers
.
CharField
(
max_length
=
100
)
name
=
serializers
.
CharField
(
max_length
=
255
)
name
=
serializers
.
CharField
(
max_length
=
255
)
min_price
=
serializers
.
IntegerField
()
min_price
=
serializers
.
IntegerField
()
suggested_prices
=
serializers
.
Char
Field
(
max_length
=
255
)
suggested_prices
=
StringList
Field
(
max_length
=
255
)
currency
=
serializers
.
CharField
(
max_length
=
8
)
currency
=
serializers
.
CharField
(
max_length
=
8
)
expiration_datetime
=
serializers
.
DateTimeField
()
expiration_datetime
=
serializers
.
DateTimeField
()
description
=
serializers
.
CharField
()
description
=
serializers
.
CharField
()
common/djangoapps/enrollment/tests/fake_data_api.py
0 → 100644
View file @
d9e47a6b
"""
A Fake Data API for testing purposes.
"""
import
copy
import
datetime
_DEFAULT_FAKE_MODE
=
{
"slug"
:
"honor"
,
"name"
:
"Honor Code Certificate"
,
"min_price"
:
0
,
"suggested_prices"
:
""
,
"currency"
:
"usd"
,
"expiration_datetime"
:
None
,
"description"
:
None
}
_ENROLLMENTS
=
[]
_COURSES
=
[]
def
get_course_enrollments
(
student_id
):
"""Stubbed out Enrollment data request."""
return
_ENROLLMENTS
def
get_course_enrollment
(
student_id
,
course_id
):
"""Stubbed out Enrollment data request."""
return
_get_fake_enrollment
(
student_id
,
course_id
)
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
:
enrollment
[
'mode'
]
=
mode
if
is_active
is
not
None
:
enrollment
[
'is_active'
]
=
is_active
return
enrollment
def
get_course_enrollment_info
(
course_id
):
"""Stubbed out Enrollment data request."""
return
_get_fake_course_info
(
course_id
)
def
_get_fake_enrollment
(
student_id
,
course_id
):
for
enrollment
in
_ENROLLMENTS
:
if
student_id
==
enrollment
[
'student'
]
and
course_id
==
enrollment
[
'course'
][
'course_id'
]:
return
enrollment
def
_get_fake_course_info
(
course_id
):
for
course
in
_COURSES
:
if
course_id
==
course
[
'course_id'
]:
return
course
def
add_enrollment
(
student_id
,
course_id
,
is_active
=
True
,
mode
=
'honor'
):
enrollment
=
{
"created"
:
datetime
.
datetime
.
now
(),
"mode"
:
mode
,
"is_active"
:
is_active
,
"course"
:
_get_fake_course_info
(
course_id
),
"student"
:
student_id
}
_ENROLLMENTS
.
append
(
enrollment
)
return
enrollment
def
add_course
(
course_id
,
enrollment_start
=
None
,
enrollment_end
=
None
,
invite_only
=
False
,
course_modes
=
None
):
course_info
=
{
"course_id"
:
course_id
,
"enrollment_end"
:
enrollment_end
,
"course_modes"
:
[],
"enrollment_start"
:
enrollment_start
,
"invite_only"
:
invite_only
,
}
if
not
course_modes
:
course_info
[
'course_modes'
]
.
append
(
_DEFAULT_FAKE_MODE
)
else
:
for
mode
in
course_modes
:
new_mode
=
copy
.
deepcopy
(
_DEFAULT_FAKE_MODE
)
new_mode
[
'slug'
]
=
mode
course_info
[
'course_modes'
]
.
append
(
new_mode
)
_COURSES
.
append
(
course_info
)
def
reset
():
global
_COURSES
_COURSES
=
[]
global
_ENROLLMENTS
_ENROLLMENTS
=
[]
common/djangoapps/enrollment/tests/test_api.py
0 → 100644
View file @
d9e47a6b
"""
Tests for student enrollment.
"""
import
ddt
from
nose.tools
import
raises
import
unittest
from
django.test
import
TestCase
from
django.test.utils
import
override_settings
from
django.conf
import
settings
from
enrollment
import
api
from
enrollment.tests
import
fake_data_api
@ddt.ddt
@override_settings
(
ENROLLMENT_DATA_API
=
"enrollment.tests.fake_data_api"
)
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
class
EnrollmentTest
(
TestCase
):
"""
Test student enrollment, especially with different course modes.
"""
USERNAME
=
"Bob"
COURSE_ID
=
"some/great/course"
def
setUp
(
self
):
fake_data_api
.
reset
()
@ddt.data
(
# Default (no course modes in the database)
# Expect automatically being enrolled as "honor".
([],
'honor'
),
# Audit / Verified / Honor
# We should always go to the "choose your course" page.
# We should also be enrolled as "honor" by default.
([
'honor'
,
'verified'
,
'audit'
],
'honor'
),
# Check for professional ed happy path.
([
'professional'
],
'professional'
)
)
@ddt.unpack
def
test_enroll
(
self
,
course_modes
,
mode
):
# Add a fake course enrollment information to the fake data API
fake_data_api
.
add_course
(
self
.
COURSE_ID
,
course_modes
=
course_modes
)
# Enroll in the course and verify the URL we get sent to
result
=
api
.
add_enrollment
(
self
.
USERNAME
,
self
.
COURSE_ID
,
mode
=
mode
)
self
.
assertIsNotNone
(
result
)
self
.
assertEquals
(
result
[
'student'
],
self
.
USERNAME
)
self
.
assertEquals
(
result
[
'course'
][
'course_id'
],
self
.
COURSE_ID
)
self
.
assertEquals
(
result
[
'mode'
],
mode
)
get_result
=
api
.
get_enrollment
(
self
.
USERNAME
,
self
.
COURSE_ID
)
self
.
assertEquals
(
result
,
get_result
)
@raises
(
api
.
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'
])
# Enroll in the course and verify the URL we get sent to
api
.
add_enrollment
(
self
.
USERNAME
,
self
.
COURSE_ID
,
mode
=
'verified'
)
@ddt.data
(
# Default (no course modes in the database)
# Expect that users are automatically enrolled as "honor".
([],
'honor'
),
# Audit / Verified / Honor
# We should always go to the "choose your course" page.
# We should also be enrolled as "honor" by default.
([
'honor'
,
'verified'
,
'audit'
],
'honor'
),
# Check for professional ed happy path.
([
'professional'
],
'professional'
)
)
@ddt.unpack
def
test_unenroll
(
self
,
course_modes
,
mode
):
# Add a fake course enrollment information to the fake data API
fake_data_api
.
add_course
(
self
.
COURSE_ID
,
course_modes
=
course_modes
)
# Enroll in the course and verify the URL we get sent to
result
=
api
.
add_enrollment
(
self
.
USERNAME
,
self
.
COURSE_ID
,
mode
=
mode
)
self
.
assertIsNotNone
(
result
)
self
.
assertEquals
(
result
[
'student'
],
self
.
USERNAME
)
self
.
assertEquals
(
result
[
'course'
][
'course_id'
],
self
.
COURSE_ID
)
self
.
assertEquals
(
result
[
'mode'
],
mode
)
self
.
assertTrue
(
result
[
'is_active'
])
result
=
api
.
deactivate_enrollment
(
self
.
USERNAME
,
self
.
COURSE_ID
)
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
)
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
)
@ddt.data
(
# Simple test of honor and verified.
([
{
'course_id'
:
'the/first/course'
,
'course_modes'
:
[],
'mode'
:
'honor'
},
{
'course_id'
:
'the/second/course'
,
'course_modes'
:
[
'honor'
,
'verified'
],
'mode'
:
'verified'
}
]),
# No enrollments
([]),
# One Enrollment
([
{
'course_id'
:
'the/third/course'
,
'course_modes'
:
[
'honor'
,
'verified'
,
'audit'
],
'mode'
:
'audit'
}
]),
)
def
test_get_all_enrollments
(
self
,
enrollments
):
for
enrollment
in
enrollments
:
fake_data_api
.
add_course
(
enrollment
[
'course_id'
],
course_modes
=
enrollment
[
'course_modes'
])
api
.
add_enrollment
(
self
.
USERNAME
,
enrollment
[
'course_id'
],
enrollment
[
'mode'
])
result
=
api
.
get_enrollments
(
self
.
USERNAME
)
self
.
assertEqual
(
len
(
enrollments
),
len
(
result
))
for
result_enrollment
in
result
:
self
.
assertIn
(
result_enrollment
[
'course'
][
'course_id'
],
[
enrollment
[
'course_id'
]
for
enrollment
in
enrollments
]
)
def
test_update_enrollment
(
self
):
# Add a fake course enrollment information to the fake data API
fake_data_api
.
add_course
(
self
.
COURSE_ID
,
course_modes
=
[
'honor'
,
'verified'
,
'audit'
])
# Enroll in the course and verify the URL we get sent to
result
=
api
.
add_enrollment
(
self
.
USERNAME
,
self
.
COURSE_ID
,
mode
=
'audit'
)
get_result
=
api
.
get_enrollment
(
self
.
USERNAME
,
self
.
COURSE_ID
)
self
.
assertEquals
(
result
,
get_result
)
result
=
api
.
update_enrollment
(
self
.
USERNAME
,
self
.
COURSE_ID
,
mode
=
'honor'
)
self
.
assertEquals
(
'honor'
,
result
[
'mode'
])
result
=
api
.
update_enrollment
(
self
.
USERNAME
,
self
.
COURSE_ID
,
mode
=
'verified'
)
self
.
assertEquals
(
'verified'
,
result
[
'mode'
])
def
test_get_course_details
(
self
):
# Add a fake course enrollment information to the fake data API
fake_data_api
.
add_course
(
self
.
COURSE_ID
,
course_modes
=
[
'honor'
,
'verified'
,
'audit'
])
result
=
api
.
get_course_enrollment_details
(
self
.
COURSE_ID
)
self
.
assertEquals
(
result
[
'course_id'
],
self
.
COURSE_ID
)
self
.
assertEquals
(
3
,
len
(
result
[
'course_modes'
]))
@override_settings
(
ENROLLMENT_DATA_API
=
'foo.bar.biz.baz'
)
@raises
(
api
.
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
0 → 100644
View file @
d9e47a6b
"""
Test the Data Aggregation Layer for Course Enrollments.
"""
import
ddt
from
nose.tools
import
raises
import
unittest
from
django.test.utils
import
override_settings
from
django.conf
import
settings
from
xmodule.modulestore.tests.django_utils
import
(
ModuleStoreTestCase
,
mixed_store_config
)
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
student.tests.factories
import
UserFactory
,
CourseModeFactory
from
student.models
import
CourseEnrollment
,
NonExistentCourseError
from
enrollment
import
data
# Since we don't need any XML course fixtures, use a modulestore configuration
# that disables the XML modulestore.
MODULESTORE_CONFIG
=
mixed_store_config
(
settings
.
COMMON_TEST_DATA_ROOT
,
{},
include_xml
=
False
)
@ddt.ddt
@override_settings
(
MODULESTORE
=
MODULESTORE_CONFIG
)
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
class
EnrollmentDataTest
(
ModuleStoreTestCase
):
"""
Test course enrollment data aggregation.
"""
USERNAME
=
"Bob"
EMAIL
=
"bob@example.com"
PASSWORD
=
"edx"
def
setUp
(
self
):
""" Create a course and user, then log in. """
super
(
EnrollmentDataTest
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
()
self
.
user
=
UserFactory
.
create
(
username
=
self
.
USERNAME
,
email
=
self
.
EMAIL
,
password
=
self
.
PASSWORD
)
self
.
client
.
login
(
username
=
self
.
USERNAME
,
password
=
self
.
PASSWORD
)
@ddt.data
(
# Default (no course modes in the database)
# Expect that users are automatically enrolled as "honor".
([],
'honor'
),
# Audit / Verified / Honor
# We should always go to the "choose your course" page.
# We should also be enrolled as "honor" by default.
([
'honor'
,
'verified'
,
'audit'
],
'honor'
),
)
@ddt.unpack
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
(
self
.
user
.
username
,
unicode
(
self
.
course
.
id
),
mode
=
enrollment_mode
,
is_active
=
True
)
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
self
.
user
,
self
.
course
.
id
))
course_mode
,
is_active
=
CourseEnrollment
.
enrollment_mode_for_user
(
self
.
user
,
self
.
course
.
id
)
self
.
assertTrue
(
is_active
)
self
.
assertEqual
(
course_mode
,
enrollment_mode
)
# Confirm the returned enrollment and the data match up.
self
.
assertEqual
(
course_mode
,
enrollment
[
'mode'
])
self
.
assertEqual
(
is_active
,
enrollment
[
'is_active'
])
def
test_unenroll
(
self
):
# Enroll the student in the course
CourseEnrollment
.
enroll
(
self
.
user
,
self
.
course
.
id
,
mode
=
"honor"
)
enrollment
=
data
.
update_course_enrollment
(
self
.
user
.
username
,
unicode
(
self
.
course
.
id
),
is_active
=
False
)
# Determine that the returned enrollment is inactive.
self
.
assertFalse
(
enrollment
[
'is_active'
])
# Expect that we're no longer enrolled
self
.
assertFalse
(
CourseEnrollment
.
is_enrolled
(
self
.
user
,
self
.
course
.
id
))
@ddt.data
(
# No course modes, no course enrollments.
([]),
# Audit / Verified / Honor course modes, with three course enrollments.
([
'honor'
,
'verified'
,
'audit'
]),
)
def
test_get_course_info
(
self
,
course_modes
):
self
.
_create_course_modes
(
course_modes
,
course
=
self
.
course
)
result_course
=
data
.
get_course_enrollment_info
(
unicode
(
self
.
course
.
id
))
result_slugs
=
[
mode
[
'slug'
]
for
mode
in
result_course
[
'course_modes'
]]
for
course_mode
in
course_modes
:
self
.
assertIn
(
course_mode
,
result_slugs
)
@ddt.data
(
# No course modes, no course enrollments.
([],
[]),
# Audit / Verified / Honor course modes, with three course enrollments.
([
'honor'
,
'verified'
,
'audit'
],
[
'1'
,
'2'
,
'3'
]),
)
@ddt.unpack
def
test_get_course_enrollments
(
self
,
course_modes
,
course_numbers
):
# Create all the courses
created_courses
=
[]
for
course_number
in
course_numbers
:
created_courses
.
append
(
CourseFactory
.
create
(
number
=
course_number
))
created_enrollments
=
[]
for
course
in
created_courses
:
self
.
_create_course_modes
(
course_modes
,
course
=
course
)
# Create the original enrollment.
created_enrollments
.
append
(
data
.
update_course_enrollment
(
self
.
user
.
username
,
unicode
(
course
.
id
),
))
# Compare the created enrollments with the results
# from the get enrollments request.
results
=
data
.
get_course_enrollments
(
self
.
user
.
username
)
self
.
assertEqual
(
results
,
created_enrollments
)
@ddt.data
(
# Default (no course modes in the database)
# Expect that users are automatically enrolled as "honor".
([],
'honor'
),
# Audit / Verified / Honor
# We should always go to the "choose your course" page.
# We should also be enrolled as "honor" by default.
([
'honor'
,
'verified'
,
'audit'
],
'verified'
),
)
@ddt.unpack
def
test_get_course_enrollment
(
self
,
course_modes
,
enrollment_mode
):
self
.
_create_course_modes
(
course_modes
)
# Try to get an enrollment before it exists.
result
=
data
.
get_course_enrollment
(
self
.
user
.
username
,
unicode
(
self
.
course
.
id
))
self
.
assertIsNone
(
result
)
# Create the original enrollment.
enrollment
=
data
.
update_course_enrollment
(
self
.
user
.
username
,
unicode
(
self
.
course
.
id
),
mode
=
enrollment_mode
,
is_active
=
True
)
# Get the enrollment and compare it to the original.
result
=
data
.
get_course_enrollment
(
self
.
user
.
username
,
unicode
(
self
.
course
.
id
))
self
.
assertEqual
(
enrollment
,
result
)
@raises
(
NonExistentCourseError
)
def
test_non_existent_course
(
self
):
data
.
get_course_enrollment_info
(
"this/is/bananas"
)
def
_create_course_modes
(
self
,
course_modes
,
course
=
None
):
course_id
=
course
.
id
if
course
else
self
.
course
.
id
for
mode_slug
in
course_modes
:
CourseModeFactory
.
create
(
course_id
=
course_id
,
mode_slug
=
mode_slug
,
mode_display_name
=
mode_slug
,
)
common/djangoapps/enrollment/tests/test_views.py
0 → 100644
View file @
d9e47a6b
"""
Tests for student enrollment.
"""
import
ddt
import
json
import
unittest
from
django.test.utils
import
override_settings
from
django.core.urlresolvers
import
reverse
from
rest_framework.test
import
APITestCase
from
rest_framework
import
status
from
django.conf
import
settings
from
xmodule.modulestore.tests.django_utils
import
(
ModuleStoreTestCase
,
mixed_store_config
)
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
student.tests.factories
import
UserFactory
,
CourseModeFactory
from
student.models
import
CourseEnrollment
# Since we don't need any XML course fixtures, use a modulestore configuration
# that disables the XML modulestore.
MODULESTORE_CONFIG
=
mixed_store_config
(
settings
.
COMMON_TEST_DATA_ROOT
,
{},
include_xml
=
False
)
@ddt.ddt
@override_settings
(
MODULESTORE
=
MODULESTORE_CONFIG
)
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
class
EnrollmentTest
(
ModuleStoreTestCase
,
APITestCase
):
"""
Test student enrollment, especially with different course modes.
"""
USERNAME
=
"Bob"
EMAIL
=
"bob@example.com"
PASSWORD
=
"edx"
def
setUp
(
self
):
""" Create a course and user, then log in. """
super
(
EnrollmentTest
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
()
self
.
user
=
UserFactory
.
create
(
username
=
self
.
USERNAME
,
email
=
self
.
EMAIL
,
password
=
self
.
PASSWORD
)
self
.
client
.
login
(
username
=
self
.
USERNAME
,
password
=
self
.
PASSWORD
)
@ddt.data
(
# Default (no course modes in the database)
# Expect that users are automatically enrolled as "honor".
([],
'honor'
),
# Audit / Verified / Honor
# We should always go to the "choose your course" page.
# We should also be enrolled as "honor" by default.
([
'honor'
,
'verified'
,
'audit'
],
'honor'
),
)
@ddt.unpack
def
test_enroll
(
self
,
course_modes
,
enrollment_mode
):
# Create the course modes (if any) required for this test case
for
mode_slug
in
course_modes
:
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
mode_slug
,
mode_display_name
=
mode_slug
,
)
# Enroll in the course and verify the URL we get sent to
self
.
_create_enrollment
()
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
self
.
user
,
self
.
course
.
id
))
course_mode
,
is_active
=
CourseEnrollment
.
enrollment_mode_for_user
(
self
.
user
,
self
.
course
.
id
)
self
.
assertTrue
(
is_active
)
self
.
assertEqual
(
course_mode
,
enrollment_mode
)
def
test_enroll_prof_ed
(
self
):
# Create the prod ed mode.
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
'professional'
,
mode_display_name
=
'Professional Education'
,
)
# 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
)
# 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'
])
def
test_unenroll
(
self
):
# Create a course mode.
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
'honor'
,
mode_display_name
=
'Honor'
,
)
# Create an enrollment
resp
=
self
.
_create_enrollment
()
# Deactivate the enrollment in the course and verify the URL we get sent to
resp
=
self
.
client
.
post
(
reverse
(
'courseenrollment'
,
kwargs
=
{
'course_id'
:
(
unicode
(
self
.
course
.
id
))}
),
{
'deactivate'
:
True
})
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
.
assertFalse
(
data
[
'is_active'
])
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_401_UNAUTHORIZED
)
def
test_unenroll_not_enrolled_in_course
(
self
):
# Deactivate the enrollment in the course and verify the URL we get sent to
resp
=
self
.
client
.
post
(
reverse
(
'courseenrollment'
,
kwargs
=
{
'course_id'
:
(
unicode
(
self
.
course
.
id
))}
),
{
'deactivate'
:
True
})
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_400_BAD_REQUEST
)
def
test_invalid_enrollment_mode
(
self
):
# Request an enrollment with verified mode, which does not exist for this course.
resp
=
self
.
client
.
post
(
reverse
(
'courseenrollment'
,
kwargs
=
{
'course_id'
:
(
unicode
(
self
.
course
.
id
))}),
{
'mode'
:
'verified'
}
)
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_400_BAD_REQUEST
)
data
=
json
.
loads
(
resp
.
content
)
self
.
assertEqual
(
unicode
(
self
.
course
.
id
),
data
[
'course_id'
])
self
.
assertEqual
(
'honor'
,
data
[
'course_modes'
][
0
][
'slug'
])
def
test_with_invalid_course_id
(
self
):
# Create an enrollment
resp
=
self
.
client
.
post
(
reverse
(
'courseenrollment'
,
kwargs
=
{
'course_id'
:
'entirely/fake/course'
}))
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_400_BAD_REQUEST
)
def
_create_enrollment
(
self
):
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'
])
return
resp
common/djangoapps/enrollment/views.py
View file @
d9e47a6b
...
@@ -3,12 +3,14 @@ The Enrollment API Views should be simple, lean HTTP endpoints for API access. T
...
@@ -3,12 +3,14 @@ The Enrollment API Views should be simple, lean HTTP endpoints for API access. T
consist primarily of authentication, request validation, and serialization.
consist primarily of authentication, request validation, and serialization.
"""
"""
from
rest_framework
import
status
from
rest_framework.authentication
import
OAuth2Authentication
,
SessionAuthentication
from
rest_framework.authentication
import
OAuth2Authentication
,
SessionAuthentication
from
rest_framework.decorators
import
api_view
,
authentication_classes
,
permission_classes
,
throttle_classes
from
rest_framework.decorators
import
api_view
,
authentication_classes
,
permission_classes
,
throttle_classes
from
rest_framework.permissions
import
IsAuthenticated
from
rest_framework.permissions
import
IsAuthenticated
from
rest_framework.response
import
Response
from
rest_framework.response
import
Response
from
rest_framework.throttling
import
UserRateThrottle
from
rest_framework.throttling
import
UserRateThrottle
from
enrollment
import
api
from
enrollment
import
api
from
student.models
import
NonExistentCourseError
class
EnrollmentUserThrottle
(
UserRateThrottle
):
class
EnrollmentUserThrottle
(
UserRateThrottle
):
...
@@ -20,6 +22,17 @@ class EnrollmentUserThrottle(UserRateThrottle):
...
@@ -20,6 +22,17 @@ class EnrollmentUserThrottle(UserRateThrottle):
@permission_classes
((
IsAuthenticated
,))
@permission_classes
((
IsAuthenticated
,))
@throttle_classes
([
EnrollmentUserThrottle
])
@throttle_classes
([
EnrollmentUserThrottle
])
def
list_student_enrollments
(
request
):
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
))
return
Response
(
api
.
get_enrollments
(
request
.
user
.
username
))
...
@@ -28,11 +41,36 @@ def list_student_enrollments(request):
...
@@ -28,11 +41,36 @@ def list_student_enrollments(request):
@permission_classes
((
IsAuthenticated
,))
@permission_classes
((
IsAuthenticated
,))
@throttle_classes
([
EnrollmentUserThrottle
])
@throttle_classes
([
EnrollmentUserThrottle
])
def
get_course_enrollment
(
request
,
course_id
=
None
):
def
get_course_enrollment
(
request
,
course_id
=
None
):
if
'mode'
in
request
.
DATA
:
"""Create, read, or update enrollment information for a student.
return
Response
(
api
.
update_enrollment
(
request
.
user
.
username
,
course_id
,
request
.
DATA
[
'mode'
]))
elif
'deactivate'
in
request
.
DATA
:
HTTP Endpoint for all CRUD operations for a student course enrollment. Allows creation, reading, and
return
Response
(
api
.
deactivate_enrollment
(
request
.
user
.
username
,
course_id
))
updates of the current enrollment for a particular course.
elif
course_id
and
request
.
method
==
'POST'
:
return
Response
(
api
.
add_enrollment
(
request
.
user
.
username
,
course_id
))
Args:
else
:
request (Request): To get current course enrollment information, a GET request will return
return
Response
(
api
.
get_enrollment
(
request
.
user
.
username
,
course_id
))
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
'mode'
in
request
.
DATA
:
return
Response
(
api
.
update_enrollment
(
request
.
user
.
username
,
course_id
,
request
.
DATA
[
'mode'
]))
elif
'deactivate'
in
request
.
DATA
:
return
Response
(
api
.
deactivate_enrollment
(
request
.
user
.
username
,
course_id
))
elif
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
)
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