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
a91d90cb
Commit
a91d90cb
authored
Apr 15, 2015
by
Will Daly
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'release'
parents
9018088c
ea52f4d8
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
213 additions
and
128 deletions
+213
-128
common/djangoapps/enrollment/tests/test_views.py
+168
-96
common/djangoapps/enrollment/urls.py
+3
-2
common/djangoapps/enrollment/views.py
+42
-30
No files found.
common/djangoapps/enrollment/tests/test_views.py
View file @
a91d90cb
"""
Tests for user enrollment.
"""
import
ddt
import
json
import
unittest
import
ddt
from
django.core.cache
import
cache
from
mock
import
patch
from
django.test
import
Client
from
django.core.handlers.wsgi
import
WSGIRequest
...
...
@@ -14,30 +15,81 @@ from rest_framework import status
from
django.conf
import
settings
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
django.test.utils
import
override_settings
from
course_modes.models
import
CourseMode
from
embargo.models
import
CountryAccessRule
,
Country
,
RestrictedCourse
from
enrollment.views
import
EnrollmentUserThrottle
from
util.models
import
RateLimitConfiguration
from
util.testing
import
UrlResetMixin
from
enrollment
import
api
from
enrollment.errors
import
CourseEnrollmentError
from
openedx.core.djangoapps.user_api.models
import
UserOrgTag
from
django.test.utils
import
override_settings
from
student.tests.factories
import
UserFactory
,
CourseModeFactory
from
student.models
import
CourseEnrollment
from
embargo.test_utils
import
restrict_course
class
EnrollmentTestMixin
(
object
):
""" Mixin with methods useful for testing enrollments. """
API_KEY
=
"i am a key"
def
assert_enrollment_status
(
self
,
course_id
=
None
,
username
=
None
,
expected_status
=
status
.
HTTP_200_OK
,
email_opt_in
=
None
,
as_server
=
False
,
mode
=
CourseMode
.
HONOR
,
):
"""
Enroll in the course and verify the response's status code. If the expected status is 200, also validates
the response content.
Returns
Response
"""
course_id
=
course_id
or
unicode
(
self
.
course
.
id
)
username
=
username
or
self
.
user
.
username
data
=
{
'mode'
:
mode
,
'course_details'
:
{
'course_id'
:
course_id
},
'user'
:
username
}
if
email_opt_in
is
not
None
:
data
[
'email_opt_in'
]
=
email_opt_in
extra
=
{}
if
as_server
:
extra
[
'HTTP_X_EDX_API_KEY'
]
=
self
.
API_KEY
url
=
reverse
(
'courseenrollments'
)
response
=
self
.
client
.
post
(
url
,
json
.
dumps
(
data
),
content_type
=
'application/json'
,
**
extra
)
self
.
assertEqual
(
response
.
status_code
,
expected_status
)
if
expected_status
in
[
status
.
HTTP_200_OK
,
status
.
HTTP_200_OK
]:
data
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
course_id
,
data
[
'course_details'
][
'course_id'
])
self
.
assertEqual
(
mode
,
data
[
'mode'
])
self
.
assertTrue
(
data
[
'is_active'
])
return
response
@override_settings
(
EDX_API_KEY
=
"i am a key"
)
@ddt.ddt
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
class
EnrollmentTest
(
ModuleStoreTestCase
,
APITestCase
):
class
EnrollmentTest
(
EnrollmentTestMixin
,
ModuleStoreTestCase
,
APITestCase
):
"""
Test user enrollment, especially with different course modes.
"""
USERNAME
=
"Bob"
EMAIL
=
"bob@example.com"
PASSWORD
=
"edx"
API_KEY
=
"i am a key"
def
setUp
(
self
):
""" Create a course and user, then log in. """
...
...
@@ -76,7 +128,7 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
)
# Create an enrollment
self
.
_create_enrollment
()
self
.
assert_enrollment_status
()
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
self
.
user
,
self
.
course
.
id
))
course_mode
,
is_active
=
CourseEnrollment
.
enrollment_mode_for_user
(
self
.
user
,
self
.
course
.
id
)
...
...
@@ -90,9 +142,9 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
mode_display_name
=
CourseMode
.
HONOR
,
)
# Create an enrollment
self
.
_create_enrollment
()
self
.
assert_enrollment_status
()
resp
=
self
.
client
.
get
(
reverse
(
'courseenrollment'
,
kwargs
=
{
"user"
:
self
.
user
.
username
,
"course_id"
:
unicode
(
self
.
course
.
id
)})
reverse
(
'courseenrollment'
,
kwargs
=
{
'username'
:
self
.
user
.
username
,
"course_id"
:
unicode
(
self
.
course
.
id
)})
)
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_200_OK
)
data
=
json
.
loads
(
resp
.
content
)
...
...
@@ -111,13 +163,14 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
Verify that the email_opt_in parameter sets the underlying flag.
And that if the argument is not present, then it does not affect the flag
"""
def
_assert_no_opt_in_set
():
""" Check the tag doesn't exit"""
with
self
.
assertRaises
(
UserOrgTag
.
DoesNotExist
):
UserOrgTag
.
objects
.
get
(
user
=
self
.
user
,
org
=
self
.
course
.
id
.
org
,
key
=
"email-optin"
)
_assert_no_opt_in_set
()
self
.
_create_enrollment
(
email_opt_in
=
opt_in
)
self
.
assert_enrollment_status
(
email_opt_in
=
opt_in
)
if
opt_in
is
None
:
_assert_no_opt_in_set
()
else
:
...
...
@@ -133,7 +186,7 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
)
# Enroll in the course, this will fail if the mode is not explicitly professional.
resp
=
self
.
_create_enrollment
(
expected_status
=
status
.
HTTP_400_BAD_REQUEST
)
resp
=
self
.
assert_enrollment_status
(
expected_status
=
status
.
HTTP_400_BAD_REQUEST
)
# While the enrollment wrong is invalid, the response content should have
# all the valid enrollment modes.
...
...
@@ -149,7 +202,7 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
mode_display_name
=
CourseMode
.
HONOR
,
)
# Create an enrollment
self
.
_create_enrollment
()
self
.
assert_enrollment_status
()
resp
=
self
.
client
.
get
(
reverse
(
'courseenrollment'
,
kwargs
=
{
"course_id"
:
unicode
(
self
.
course
.
id
)})
)
...
...
@@ -164,7 +217,7 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
self
.
client
.
logout
()
# Try to enroll, this should fail.
self
.
_create_enrollment
(
expected_status
=
status
.
HTTP_401_UNAUTHORIZED
)
self
.
assert_enrollment_status
(
expected_status
=
status
.
HTTP_401_UNAUTHORIZED
)
def
test_user_not_activated
(
self
):
# Log out the default user, Bob.
...
...
@@ -187,7 +240,7 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
self
.
user
.
save
()
# Enrollment should succeed, even though we haven't authenticated.
self
.
_create_enrollment
()
self
.
assert_enrollment_status
()
def
test_user_does_not_match_url
(
self
):
# Try to enroll a user that is not the authenticated user.
...
...
@@ -196,10 +249,10 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
mode_slug
=
CourseMode
.
HONOR
,
mode_display_name
=
CourseMode
.
HONOR
,
)
self
.
_create_enrollment
(
username
=
self
.
other_user
.
username
,
expected_status
=
status
.
HTTP_404_NOT_FOUND
)
self
.
assert_enrollment_status
(
username
=
self
.
other_user
.
username
,
expected_status
=
status
.
HTTP_404_NOT_FOUND
)
# Verify that the server still has access to this endpoint.
self
.
client
.
logout
()
self
.
_create_enrollment
(
username
=
self
.
other_user
.
username
,
as_server
=
True
)
self
.
assert_enrollment_status
(
username
=
self
.
other_user
.
username
,
as_server
=
True
)
def
test_user_does_not_match_param_for_list
(
self
):
CourseModeFactory
.
create
(
...
...
@@ -207,12 +260,12 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
mode_slug
=
CourseMode
.
HONOR
,
mode_display_name
=
CourseMode
.
HONOR
,
)
resp
=
self
.
client
.
get
(
reverse
(
'courseenrollments'
),
{
"user"
:
self
.
other_user
.
username
})
resp
=
self
.
client
.
get
(
reverse
(
'courseenrollments'
),
{
'user'
:
self
.
other_user
.
username
})
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_404_NOT_FOUND
)
# Verify that the server still has access to this endpoint.
self
.
client
.
logout
()
resp
=
self
.
client
.
get
(
reverse
(
'courseenrollments'
),
{
"user"
:
self
.
other_user
.
username
},
**
{
'HTTP_X_EDX_API_KEY'
:
self
.
API_KEY
}
reverse
(
'courseenrollments'
),
{
'username'
:
self
.
other_user
.
username
},
**
{
'HTTP_X_EDX_API_KEY'
:
self
.
API_KEY
}
)
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_200_OK
)
...
...
@@ -222,16 +275,15 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
mode_slug
=
CourseMode
.
HONOR
,
mode_display_name
=
CourseMode
.
HONOR
,
)
resp
=
self
.
client
.
get
(
reverse
(
'courseenrollment'
,
kwargs
=
{
"user"
:
self
.
other_user
.
username
,
"course_id"
:
unicode
(
self
.
course
.
id
)})
)
url
=
reverse
(
'courseenrollment'
,
kwargs
=
{
'username'
:
self
.
other_user
.
username
,
"course_id"
:
unicode
(
self
.
course
.
id
)})
resp
=
self
.
client
.
get
(
url
)
# Verify that the server still has access to this endpoint.
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_404_NOT_FOUND
)
self
.
client
.
logout
()
resp
=
self
.
client
.
get
(
reverse
(
'courseenrollment'
,
kwargs
=
{
"user"
:
self
.
other_user
.
username
,
"course_id"
:
unicode
(
self
.
course
.
id
)}),
**
{
'HTTP_X_EDX_API_KEY'
:
self
.
API_KEY
}
)
resp
=
self
.
client
.
get
(
url
,
**
{
'HTTP_X_EDX_API_KEY'
:
self
.
API_KEY
})
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_200_OK
)
def
test_get_course_details
(
self
):
...
...
@@ -254,7 +306,7 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
self
.
assertEqual
(
mode
[
'name'
],
CourseMode
.
HONOR
)
def
test_with_invalid_course_id
(
self
):
self
.
_create_enrollment
(
course_id
=
'entirely/fake/course'
,
expected_status
=
status
.
HTTP_400_BAD_REQUEST
)
self
.
assert_enrollment_status
(
course_id
=
'entirely/fake/course'
,
expected_status
=
status
.
HTTP_400_BAD_REQUEST
)
def
test_get_enrollment_details_bad_course
(
self
):
resp
=
self
.
client
.
get
(
...
...
@@ -266,13 +318,13 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
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
)})
reverse
(
'courseenrollment'
,
kwargs
=
{
'username'
:
self
.
user
.
username
,
"course_id"
:
unicode
(
self
.
course
.
id
)})
)
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_400_BAD_REQUEST
)
def
test_enrollment_already_enrolled
(
self
):
response
=
self
.
_create_enrollment
()
repeat_response
=
self
.
_create_enrollment
(
expected_status
=
status
.
HTTP_200_OK
)
response
=
self
.
assert_enrollment_status
()
repeat_response
=
self
.
assert_enrollment_status
(
expected_status
=
status
.
HTTP_200_OK
)
self
.
assertEqual
(
json
.
loads
(
response
.
content
),
json
.
loads
(
repeat_response
.
content
))
def
test_get_enrollment_with_invalid_key
(
self
):
...
...
@@ -301,7 +353,7 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
for
attempt
in
xrange
(
self
.
rate_limit
+
10
):
expected_status
=
status
.
HTTP_429_TOO_MANY_REQUESTS
if
attempt
>=
self
.
rate_limit
else
status
.
HTTP_200_OK
self
.
_create_enrollment
(
expected_status
=
expected_status
)
self
.
assert_enrollment_status
(
expected_status
=
expected_status
)
def
test_enrollment_throttle_for_service
(
self
):
"""Make sure a service can call the enrollment API as many times as needed. """
...
...
@@ -314,7 +366,7 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
)
for
attempt
in
xrange
(
self
.
rate_limit
+
10
):
self
.
_create_enrollment
(
as_server
=
True
)
self
.
assert_enrollment_status
(
as_server
=
True
)
def
test_create_enrollment_with_mode
(
self
):
"""With the right API key, create a new enrollment with a mode set other than the default."""
...
...
@@ -326,7 +378,7 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
)
# Create an enrollment
self
.
_create_enrollment
(
as_server
=
True
,
mode
=
'professional'
)
self
.
assert_enrollment_status
(
as_server
=
True
,
mode
=
'professional'
)
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
self
.
user
,
self
.
course
.
id
))
course_mode
,
is_active
=
CourseEnrollment
.
enrollment_mode_for_user
(
self
.
user
,
self
.
course
.
id
)
...
...
@@ -344,7 +396,7 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
)
# Create an enrollment
self
.
_create_enrollment
(
as_server
=
True
)
self
.
assert_enrollment_status
(
as_server
=
True
)
# Check that the enrollment is honor.
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
self
.
user
,
self
.
course
.
id
))
...
...
@@ -353,7 +405,7 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
self
.
assertEqual
(
course_mode
,
CourseMode
.
HONOR
)
# Check that the enrollment upgraded to verified.
self
.
_create_enrollment
(
as_server
=
True
,
mode
=
CourseMode
.
VERIFIED
,
expected_status
=
status
.
HTTP_200_OK
)
self
.
assert_enrollment_status
(
as_server
=
True
,
mode
=
CourseMode
.
VERIFIED
,
expected_status
=
status
.
HTTP_200_OK
)
course_mode
,
is_active
=
CourseEnrollment
.
enrollment_mode_for_user
(
self
.
user
,
self
.
course
.
id
)
self
.
assertTrue
(
is_active
)
self
.
assertEqual
(
course_mode
,
CourseMode
.
VERIFIED
)
...
...
@@ -369,7 +421,7 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
)
# Create a 'verified' enrollment
self
.
_create_enrollment
(
as_server
=
True
,
mode
=
CourseMode
.
VERIFIED
)
self
.
assert_enrollment_status
(
as_server
=
True
,
mode
=
CourseMode
.
VERIFIED
)
# Check that the enrollment is verified.
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
self
.
user
,
self
.
course
.
id
))
...
...
@@ -378,7 +430,7 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
self
.
assertEqual
(
course_mode
,
CourseMode
.
VERIFIED
)
# Check that the enrollment downgraded to honor.
self
.
_create_enrollment
(
as_server
=
True
,
mode
=
CourseMode
.
HONOR
,
expected_status
=
status
.
HTTP_200_OK
)
self
.
assert_enrollment_status
(
as_server
=
True
,
mode
=
CourseMode
.
HONOR
,
expected_status
=
status
.
HTTP_200_OK
)
course_mode
,
is_active
=
CourseEnrollment
.
enrollment_mode_for_user
(
self
.
user
,
self
.
course
.
id
)
self
.
assertTrue
(
is_active
)
self
.
assertEqual
(
course_mode
,
CourseMode
.
HONOR
)
...
...
@@ -394,7 +446,7 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
)
# Create an enrollment
self
.
_create_enrollment
()
self
.
assert_enrollment_status
()
# Check that the enrollment is honor.
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
self
.
user
,
self
.
course
.
id
))
...
...
@@ -403,50 +455,23 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
self
.
assertEqual
(
course_mode
,
CourseMode
.
HONOR
)
# Get a 403 response when trying to upgrade yourself.
self
.
_create_enrollment
(
mode
=
CourseMode
.
VERIFIED
,
expected_status
=
status
.
HTTP_403_FORBIDDEN
)
self
.
assert_enrollment_status
(
mode
=
CourseMode
.
VERIFIED
,
expected_status
=
status
.
HTTP_403_FORBIDDEN
)
course_mode
,
is_active
=
CourseEnrollment
.
enrollment_mode_for_user
(
self
.
user
,
self
.
course
.
id
)
self
.
assertTrue
(
is_active
)
self
.
assertEqual
(
course_mode
,
CourseMode
.
HONOR
)
def
_create_enrollment
(
self
,
course_id
=
None
,
username
=
None
,
expected_status
=
status
.
HTTP_200_OK
,
email_opt_in
=
None
,
as_server
=
False
,
mode
=
CourseMode
.
HONOR
,
):
"""Enroll in the course and verify the URL we are sent to. """
course_id
=
unicode
(
self
.
course
.
id
)
if
course_id
is
None
else
course_id
username
=
self
.
user
.
username
if
username
is
None
else
username
params
=
{
'mode'
:
mode
,
'course_details'
:
{
'course_id'
:
course_id
},
'user'
:
username
}
if
email_opt_in
is
not
None
:
params
[
'email_opt_in'
]
=
email_opt_in
if
as_server
:
resp
=
self
.
client
.
post
(
reverse
(
'courseenrollments'
),
params
,
format
=
'json'
,
**
{
'HTTP_X_EDX_API_KEY'
:
self
.
API_KEY
})
else
:
resp
=
self
.
client
.
post
(
reverse
(
'courseenrollments'
),
params
,
format
=
'json'
)
self
.
assertEqual
(
resp
.
status_code
,
expected_status
)
if
expected_status
in
[
status
.
HTTP_200_OK
,
status
.
HTTP_200_OK
]:
data
=
json
.
loads
(
resp
.
content
)
self
.
assertEqual
(
course_id
,
data
[
'course_details'
][
'course_id'
])
self
.
assertEqual
(
mode
,
data
[
'mode'
])
self
.
assertTrue
(
data
[
'is_active'
])
return
resp
def
test_change_mode_invalid_user
(
self
):
"""
Attempts to change an enrollment for a non-existent user should result in an HTTP 404 for non-server users,
and HTTP 406 for server users.
"""
self
.
assert_enrollment_status
(
username
=
'fake-user'
,
expected_status
=
status
.
HTTP_404_NOT_FOUND
,
as_server
=
False
)
self
.
assert_enrollment_status
(
username
=
'fake-user'
,
expected_status
=
status
.
HTTP_406_NOT_ACCEPTABLE
,
as_server
=
True
)
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
class
EnrollmentEmbargoTest
(
UrlResetMixin
,
ModuleStoreTestCase
):
class
EnrollmentEmbargoTest
(
EnrollmentTestMixin
,
UrlResetMixin
,
ModuleStoreTestCase
):
"""Test that enrollment is blocked from embargoed countries. """
USERNAME
=
"Bob"
...
...
@@ -460,51 +485,98 @@ class EnrollmentEmbargoTest(UrlResetMixin, ModuleStoreTestCase):
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
)
self
.
url
=
reverse
(
'courseenrollments'
)
@patch.dict
(
settings
.
FEATURES
,
{
'EMBARGO'
:
True
})
def
test_embargo_change_enrollment_restrict
(
self
):
url
=
reverse
(
'courseenrollments'
)
data
=
json
.
dumps
({
def
_generate_data
(
self
):
return
json
.
dumps
({
'course_details'
:
{
'course_id'
:
unicode
(
self
.
course
.
id
)
},
'user'
:
self
.
user
.
username
})
# Attempt to enroll from a country embargoed for this course
with
restrict_course
(
self
.
course
.
id
)
as
redirect_url
:
response
=
self
.
client
.
post
(
url
,
data
,
content_type
=
'application/json'
)
def
assert_access_denied
(
self
,
user_message_url
):
"""
Verify that the view returns HTTP status 403 and includes a URL in the response, and no enrollment is created.
"""
data
=
self
.
_generate_data
()
response
=
self
.
client
.
post
(
self
.
url
,
data
,
content_type
=
'application/json'
)
# Expect an error response
self
.
assertEqual
(
response
.
status_code
,
403
)
# Expect an error response
self
.
assertEqual
(
response
.
status_code
,
403
)
# Expect that the redirect URL is included in the response
resp_data
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
resp_data
[
'user_message_url'
],
redirect
_url
)
# Expect that the redirect URL is included in the response
resp_data
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
resp_data
[
'user_message_url'
],
user_message
_url
)
# Verify that we were not enrolled
self
.
assertEqual
(
self
.
_get_enrollments
(),
[])
@patch.dict
(
settings
.
FEATURES
,
{
'EMBARGO'
:
True
})
def
test_embargo_change_enrollment_allow
(
self
):
url
=
reverse
(
'courseenrollments'
)
data
=
json
.
dumps
({
'course_details'
:
{
'course_id'
:
unicode
(
self
.
course
.
id
)
},
'user'
:
self
.
user
.
username
})
def
test_embargo_change_enrollment_restrict_geoip
(
self
):
""" Validates that enrollment changes are blocked if the request originates from an embargoed country. """
# Use the helper to setup the embargo and simulate a request from a blocked IP address.
with
restrict_course
(
self
.
course
.
id
)
as
redirect_url
:
self
.
assert_access_denied
(
redirect_url
)
def
_setup_embargo
(
self
):
restricted_course
=
RestrictedCourse
.
objects
.
create
(
course_key
=
self
.
course
.
id
)
restricted_country
=
Country
.
objects
.
create
(
country
=
'US'
)
unrestricted_country
=
Country
.
objects
.
create
(
country
=
'CA'
)
CountryAccessRule
.
objects
.
create
(
rule_type
=
CountryAccessRule
.
BLACKLIST_RULE
,
restricted_course
=
restricted_course
,
country
=
restricted_country
)
# Clear the cache to remove the effects of previous embargo tests
cache
.
clear
()
return
unrestricted_country
,
restricted_country
@override_settings
(
EDX_API_KEY
=
EnrollmentTestMixin
.
API_KEY
)
@patch.dict
(
settings
.
FEATURES
,
{
'EMBARGO'
:
True
})
def
test_embargo_change_enrollment_restrict_user_profile
(
self
):
""" Validates that enrollment changes are blocked if the user's profile is linked to an embargoed country. """
response
=
self
.
client
.
post
(
url
,
data
,
content_type
=
'application/json'
)
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
__
,
restricted_country
=
self
.
_setup_embargo
()
# Update the user's profile, linking the user to the embargoed country.
self
.
user
.
profile
.
country
=
restricted_country
.
country
self
.
user
.
profile
.
save
()
user_message_url
=
reverse
(
'embargo_blocked_message'
,
kwargs
=
{
'access_point'
:
'enrollment'
,
'message_key'
:
'default'
})
self
.
assert_access_denied
(
user_message_url
)
@override_settings
(
EDX_API_KEY
=
EnrollmentTestMixin
.
API_KEY
)
@patch.dict
(
settings
.
FEATURES
,
{
'EMBARGO'
:
True
})
def
test_embargo_change_enrollment_allow_user_profile
(
self
):
"""
Validates that enrollment changes are allowed if the user's profile is NOT linked to an embargoed country.
"""
# Setup the embargo
unrestricted_country
,
__
=
self
.
_setup_embargo
()
# Verify that users without black-listed country codes *can* be enrolled
self
.
user
.
profile
.
country
=
unrestricted_country
.
country
self
.
user
.
profile
.
save
()
self
.
assert_enrollment_status
()
@patch.dict
(
settings
.
FEATURES
,
{
'EMBARGO'
:
True
})
def
test_embargo_change_enrollment_allow
(
self
):
self
.
assert_enrollment_status
()
# Verify that we were enrolled
self
.
assertEqual
(
len
(
self
.
_get_enrollments
()),
1
)
def
_get_enrollments
(
self
):
"""Retrieve the enrollment list for the current user. """
url
=
reverse
(
'courseenrollments'
)
resp
=
self
.
client
.
get
(
url
)
resp
=
self
.
client
.
get
(
self
.
url
)
return
json
.
loads
(
resp
.
content
)
...
...
common/djangoapps/enrollment/urls.py
View file @
a91d90cb
...
...
@@ -11,12 +11,13 @@ from .views import (
EnrollmentCourseDetailView
)
USER
_PATTERN
=
'(?P<user
>[
\
w.@+-]+)'
USER
NAME_PATTERN
=
'(?P<username
>[
\
w.@+-]+)'
urlpatterns
=
patterns
(
'enrollment.views'
,
url
(
r'^enrollment/{user},{course_key}$'
.
format
(
user
=
USER_PATTERN
,
course_key
=
settings
.
COURSE_ID_PATTERN
),
r'^enrollment/{username},{course_key}$'
.
format
(
username
=
USERNAME_PATTERN
,
course_key
=
settings
.
COURSE_ID_PATTERN
),
EnrollmentView
.
as_view
(),
name
=
'courseenrollment'
),
...
...
common/djangoapps/enrollment/views.py
View file @
a91d90cb
...
...
@@ -4,6 +4,7 @@ consist primarily of authentication, request validation, and serialization.
"""
from
ipware.ip
import
get_ip
from
django.core.exceptions
import
ObjectDoesNotExist
from
django.utils.decorators
import
method_decorator
from
opaque_keys
import
InvalidKeyError
from
course_modes.models
import
CourseMode
...
...
@@ -24,6 +25,7 @@ from enrollment.errors import (
CourseNotFoundError
,
CourseEnrollmentError
,
CourseModeNotFoundError
,
CourseEnrollmentExistsError
)
from
student.models
import
User
class
EnrollmentCrossDomainSessionAuth
(
SessionAuthenticationAllowInactiveUser
,
SessionAuthenticationCrossDomainCsrf
):
...
...
@@ -104,11 +106,10 @@ class EnrollmentView(APIView, ApiKeyPermissionMixIn):
permission_classes
=
ApiKeyHeaderPermissionIsAuthenticated
,
throttle_classes
=
EnrollmentUserThrottle
,
# Since the course about page on the marketing site
# uses this API to auto-enroll users, we need to support
# cross-domain CSRF.
# Since the course about page on the marketing site uses this API to auto-enroll users,
# we need to support cross-domain CSRF.
@method_decorator
(
ensure_csrf_cookie_cross_domain
)
def
get
(
self
,
request
,
course_id
=
None
,
user
=
None
):
def
get
(
self
,
request
,
course_id
=
None
,
user
name
=
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
...
...
@@ -119,27 +120,29 @@ class EnrollmentView(APIView, ApiKeyPermissionMixIn):
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.
user
name (str): The
username associated with this enrollment request.
Return:
A JSON serialized representation of the course enrollment.
"""
user
=
user
if
user
else
request
.
user
.
username
if
request
.
user
.
username
!=
user
and
not
self
.
has_api_key_permissions
(
request
):
username
=
username
or
request
.
user
.
username
if
request
.
user
.
username
!=
username
and
not
self
.
has_api_key_permissions
(
request
):
# 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
))
return
Response
(
api
.
get_enrollment
(
user
name
,
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
)
u"'{user
name
}' in course '{course_id}'"
)
.
format
(
user
name
=
username
,
course_id
=
course_id
)
}
)
...
...
@@ -295,20 +298,20 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
"""
Gets a list of all course enrollments for the currently logged in user.
"""
user
=
request
.
GET
.
get
(
'user'
,
request
.
user
.
username
)
if
request
.
user
.
username
!=
user
and
not
self
.
has_api_key_permissions
(
request
):
user
name
=
request
.
GET
.
get
(
'user'
,
request
.
user
.
username
)
if
request
.
user
.
username
!=
user
name
and
not
self
.
has_api_key_permissions
(
request
):
# 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
))
return
Response
(
api
.
get_enrollments
(
user
name
))
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
)
u"An error occurred while retrieving enrollments for user '{user
name
}'"
)
.
format
(
user
name
=
username
)
}
)
...
...
@@ -317,14 +320,15 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
Enrolls the currently logged in user in a course.
"""
# Get the User, Course ID, and Mode from the request.
user
=
request
.
DATA
.
get
(
'user'
,
request
.
user
.
username
)
username
=
request
.
DATA
.
get
(
'user'
,
request
.
user
.
username
)
course_id
=
request
.
DATA
.
get
(
'course_details'
,
{})
.
get
(
'course_id'
)
if
'course_details'
not
in
request
.
DATA
or
'course_id'
not
in
request
.
DATA
[
'course_details'
]
:
if
not
course_id
:
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
:
course_id
=
CourseKey
.
from_string
(
course_id
)
except
InvalidKeyError
:
...
...
@@ -340,9 +344,9 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
has_api_key_permissions
=
self
.
has_api_key_permissions
(
request
)
# Check that the user specified is either the same user, or this is a server-to-server request.
if
not
user
:
user
=
request
.
user
.
username
if
user
!=
request
.
user
.
username
and
not
has_api_key_permissions
:
if
not
user
name
:
user
name
=
request
.
user
.
username
if
user
name
!=
request
.
user
.
username
and
not
has_api_key_permissions
:
# 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
)
...
...
@@ -357,14 +361,22 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
}
)
try
:
# Lookup the user, instead of using request.user, since request.user may not match the username POSTed.
user
=
User
.
objects
.
get
(
username
=
username
)
except
ObjectDoesNotExist
:
return
Response
(
status
=
status
.
HTTP_406_NOT_ACCEPTABLE
,
data
=
{
'message'
:
u'The user {} does not exist.'
.
format
(
username
)
}
)
# Check whether any country access rules block the user from enrollment
# We do this at the view level (rather than the Python API level)
# because this check requires information about the HTTP request.
redirect_url
=
embargo_api
.
redirect_if_blocked
(
course_id
,
user
=
user
,
ip_address
=
get_ip
(
request
),
url
=
request
.
path
)
course_id
,
user
=
user
,
ip_address
=
get_ip
(
request
),
url
=
request
.
path
)
if
redirect_url
:
return
Response
(
status
=
status
.
HTTP_403_FORBIDDEN
,
...
...
@@ -384,11 +396,11 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
# Only server-to-server calls will currently be allowed to modify the mode for existing enrollments. All
# other requests will go through add_enrollment(), which will allow creating of new enrollments, and
# re-activating enrollments
enrollment
=
api
.
get_enrollment
(
user
,
unicode
(
course_id
))
enrollment
=
api
.
get_enrollment
(
user
name
,
unicode
(
course_id
))
if
has_api_key_permissions
and
enrollment
and
enrollment
[
'mode'
]
!=
mode
:
response
=
api
.
update_enrollment
(
user
,
unicode
(
course_id
),
mode
=
mode
)
response
=
api
.
update_enrollment
(
user
name
,
unicode
(
course_id
),
mode
=
mode
)
else
:
response
=
api
.
add_enrollment
(
user
,
unicode
(
course_id
),
mode
=
mode
)
response
=
api
.
add_enrollment
(
user
name
,
unicode
(
course_id
),
mode
=
mode
)
email_opt_in
=
request
.
DATA
.
get
(
'email_opt_in'
,
None
)
if
email_opt_in
is
not
None
:
org
=
course_id
.
org
...
...
@@ -418,7 +430,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
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
)
u"'{user
name
}' in course '{course_id}'"
)
.
format
(
user
name
=
username
,
course_id
=
course_id
)
}
)
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