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
70ec4c5f
Commit
70ec4c5f
authored
Feb 11, 2015
by
Will Daly
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #6948 from edx/will/country-access-part-2
Finish Country Access (Part 2 of 3)
parents
ea29bb42
e609f982
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
337 additions
and
24 deletions
+337
-24
common/djangoapps/course_modes/tests/test_views.py
+33
-1
common/djangoapps/course_modes/views.py
+15
-0
common/djangoapps/enrollment/tests/test_views.py
+66
-4
common/djangoapps/enrollment/views.py
+35
-9
common/djangoapps/student/tests/test_enrollment.py
+28
-3
common/djangoapps/student/views.py
+14
-0
lms/djangoapps/shoppingcart/tests/test_views.py
+48
-3
lms/djangoapps/shoppingcart/views.py
+22
-0
lms/djangoapps/verify_student/tests/test_views.py
+19
-2
lms/djangoapps/verify_student/views.py
+14
-0
lms/static/js/spec/student_account/enrollment_spec.js
+23
-1
lms/static/js/student_account/enrollment.js
+20
-1
No files found.
common/djangoapps/course_modes/tests/test_views.py
View file @
70ec4c5f
...
@@ -11,6 +11,7 @@ from xmodule.modulestore.tests.django_utils import (
...
@@ -11,6 +11,7 @@ from xmodule.modulestore.tests.django_utils import (
)
)
from
util.testing
import
UrlResetMixin
from
util.testing
import
UrlResetMixin
from
embargo.test_utils
import
restrict_course
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
course_modes.tests.factories
import
CourseModeFactory
from
course_modes.tests.factories
import
CourseModeFactory
from
student.tests.factories
import
CourseEnrollmentFactory
,
UserFactory
from
student.tests.factories
import
CourseEnrollmentFactory
,
UserFactory
...
@@ -274,7 +275,7 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
...
@@ -274,7 +275,7 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
# Create a verified mode
# Create a verified mode
url
=
reverse
(
'create_mode'
,
args
=
[
unicode
(
self
.
course
.
id
)])
url
=
reverse
(
'create_mode'
,
args
=
[
unicode
(
self
.
course
.
id
)])
response
=
self
.
client
.
get
(
url
,
parameters
)
self
.
client
.
get
(
url
,
parameters
)
honor_mode
=
Mode
(
u'honor'
,
u'Honor Code Certificate'
,
0
,
''
,
'usd'
,
None
,
None
)
honor_mode
=
Mode
(
u'honor'
,
u'Honor Code Certificate'
,
0
,
''
,
'usd'
,
None
,
None
)
verified_mode
=
Mode
(
u'verified'
,
u'Verified Certificate'
,
10
,
'10,20'
,
'usd'
,
None
,
None
)
verified_mode
=
Mode
(
u'verified'
,
u'Verified Certificate'
,
10
,
'10,20'
,
'usd'
,
None
,
None
)
...
@@ -282,3 +283,34 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
...
@@ -282,3 +283,34 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
course_modes
=
CourseMode
.
modes_for_course
(
self
.
course
.
id
)
course_modes
=
CourseMode
.
modes_for_course
(
self
.
course
.
id
)
self
.
assertEquals
(
course_modes
,
expected_modes
)
self
.
assertEquals
(
course_modes
,
expected_modes
)
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
class
TrackSelectionEmbargoTest
(
UrlResetMixin
,
ModuleStoreTestCase
):
"""Test embargo restrictions on the track selection page. """
@patch.dict
(
settings
.
FEATURES
,
{
'ENABLE_COUNTRY_ACCESS'
:
True
})
def
setUp
(
self
):
super
(
TrackSelectionEmbargoTest
,
self
)
.
setUp
(
'embargo'
)
# Create a course and course modes
self
.
course
=
CourseFactory
.
create
()
CourseModeFactory
(
mode_slug
=
'honor'
,
course_id
=
self
.
course
.
id
)
CourseModeFactory
(
mode_slug
=
'verified'
,
course_id
=
self
.
course
.
id
,
min_price
=
10
)
# Create a user and log in
self
.
user
=
UserFactory
.
create
(
username
=
"Bob"
,
email
=
"bob@example.com"
,
password
=
"edx"
)
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
"edx"
)
# Construct the URL for the track selection page
self
.
url
=
reverse
(
'course_modes_choose'
,
args
=
[
unicode
(
self
.
course
.
id
)])
@patch.dict
(
settings
.
FEATURES
,
{
'ENABLE_COUNTRY_ACCESS'
:
True
})
def
test_embargo_restrict
(
self
):
with
restrict_course
(
self
.
course
.
id
)
as
redirect_url
:
response
=
self
.
client
.
get
(
self
.
url
)
self
.
assertRedirects
(
response
,
redirect_url
)
def
test_embargo_allow
(
self
):
response
=
self
.
client
.
get
(
self
.
url
)
self
.
assertEqual
(
response
.
status_code
,
200
)
common/djangoapps/course_modes/views.py
View file @
70ec4c5f
...
@@ -3,6 +3,8 @@ Views for the course_mode module
...
@@ -3,6 +3,8 @@ Views for the course_mode module
"""
"""
import
decimal
import
decimal
from
ipware.ip
import
get_ip
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
django.conf
import
settings
from
django.conf
import
settings
from
django.http
import
HttpResponse
,
HttpResponseBadRequest
from
django.http
import
HttpResponse
,
HttpResponseBadRequest
...
@@ -22,6 +24,8 @@ from opaque_keys.edx.keys import CourseKey
...
@@ -22,6 +24,8 @@ from opaque_keys.edx.keys import CourseKey
from
util.db
import
commit_on_success_with_read_committed
from
util.db
import
commit_on_success_with_read_committed
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
embargo
import
api
as
embargo_api
class
ChooseModeView
(
View
):
class
ChooseModeView
(
View
):
"""View used when the user is asked to pick a mode.
"""View used when the user is asked to pick a mode.
...
@@ -52,6 +56,17 @@ class ChooseModeView(View):
...
@@ -52,6 +56,17 @@ class ChooseModeView(View):
"""
"""
course_key
=
CourseKey
.
from_string
(
course_id
)
course_key
=
CourseKey
.
from_string
(
course_id
)
# Check whether the user has access to this course
# based on country access rules.
embargo_redirect
=
embargo_api
.
redirect_if_blocked
(
course_key
,
user
=
request
.
user
,
ip_address
=
get_ip
(
request
),
url
=
request
.
path
)
if
embargo_redirect
:
return
redirect
(
embargo_redirect
)
enrollment_mode
,
is_active
=
CourseEnrollment
.
enrollment_mode_for_user
(
request
.
user
,
course_key
)
enrollment_mode
,
is_active
=
CourseEnrollment
.
enrollment_mode_for_user
(
request
.
user
,
course_key
)
modes
=
CourseMode
.
modes_for_course_dict
(
course_key
)
modes
=
CourseMode
.
modes_for_course_dict
(
course_key
)
...
...
common/djangoapps/enrollment/tests/test_views.py
View file @
70ec4c5f
...
@@ -6,19 +6,18 @@ import json
...
@@ -6,19 +6,18 @@ import json
import
unittest
import
unittest
from
mock
import
patch
from
mock
import
patch
from
django.test.utils
import
override_settings
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
rest_framework.test
import
APITestCase
from
rest_framework.test
import
APITestCase
from
rest_framework
import
status
from
rest_framework
import
status
from
django.conf
import
settings
from
django.conf
import
settings
from
xmodule.modulestore.tests.django_utils
import
(
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
ModuleStoreTestCase
,
mixed_store_config
)
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
util.testing
import
UrlResetMixin
from
enrollment
import
api
from
enrollment
import
api
from
enrollment.errors
import
CourseEnrollmentError
from
enrollment.errors
import
CourseEnrollmentError
from
student.tests.factories
import
UserFactory
,
CourseModeFactory
from
student.tests.factories
import
UserFactory
,
CourseModeFactory
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
from
embargo.test_utils
import
restrict_course
@ddt.ddt
@ddt.ddt
...
@@ -245,3 +244,66 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
...
@@ -245,3 +244,66 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
)
)
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_400_BAD_REQUEST
)
self
.
assertEqual
(
resp
.
status_code
,
status
.
HTTP_400_BAD_REQUEST
)
self
.
assertIn
(
"No course "
,
resp
.
content
)
self
.
assertIn
(
"No course "
,
resp
.
content
)
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
class
EnrollmentEmbargoTest
(
UrlResetMixin
,
ModuleStoreTestCase
):
"""Test that enrollment is blocked from embargoed countries. """
USERNAME
=
"Bob"
EMAIL
=
"bob@example.com"
PASSWORD
=
"edx"
@patch.dict
(
settings
.
FEATURES
,
{
'ENABLE_COUNTRY_ACCESS'
:
True
})
def
setUp
(
self
):
""" Create a course and user, then log in. """
super
(
EnrollmentEmbargoTest
,
self
)
.
setUp
(
'embargo'
)
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
)
@patch.dict
(
settings
.
FEATURES
,
{
'ENABLE_COUNTRY_ACCESS'
:
True
})
def
test_embargo_change_enrollment_restrict
(
self
):
url
=
reverse
(
'courseenrollments'
)
data
=
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'
)
# 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
)
# Verify that we were not enrolled
self
.
assertEqual
(
self
.
_get_enrollments
(),
[])
@patch.dict
(
settings
.
FEATURES
,
{
'ENABLE_COUNTRY_ACCESS'
:
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
})
response
=
self
.
client
.
post
(
url
,
data
,
content_type
=
'application/json'
)
self
.
assertEqual
(
response
.
status_code
,
200
)
# 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
)
return
json
.
loads
(
resp
.
content
)
common/djangoapps/enrollment/views.py
View file @
70ec4c5f
...
@@ -3,15 +3,19 @@ The Enrollment API Views should be simple, lean HTTP endpoints for API access. T
...
@@ -3,15 +3,19 @@ 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
opaque_keys
import
InvalidKeyError
from
ipware.ip
import
get_ip
from
django.conf
import
settings
from
rest_framework
import
status
from
rest_framework
import
status
from
rest_framework.authentication
import
OAuth2Authentication
from
rest_framework.authentication
import
OAuth2Authentication
from
rest_framework
import
permissions
from
rest_framework
import
permissions
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
rest_framework.views
import
APIView
from
rest_framework.views
import
APIView
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys
import
InvalidKeyError
from
enrollment
import
api
from
enrollment
import
api
from
enrollment.errors
import
CourseNotFoundError
,
CourseEnrollmentError
,
CourseModeNotFoundError
from
enrollment.errors
import
CourseNotFoundError
,
CourseEnrollmentError
,
CourseModeNotFoundError
from
embargo
import
api
as
embargo_api
from
util.authentication
import
SessionAuthenticationAllowInactiveUser
from
util.authentication
import
SessionAuthenticationAllowInactiveUser
from
util.disable_rate_limit
import
can_disable_rate_limit
from
util.disable_rate_limit
import
can_disable_rate_limit
...
@@ -278,7 +282,36 @@ class EnrollmentListView(APIView):
...
@@ -278,7 +282,36 @@ class EnrollmentListView(APIView):
course_id
=
request
.
DATA
[
'course_details'
][
'course_id'
]
course_id
=
request
.
DATA
[
'course_details'
][
'course_id'
]
try
:
try
:
return
Response
(
api
.
add_enrollment
(
user
,
course_id
))
course_id
=
CourseKey
.
from_string
(
course_id
)
except
InvalidKeyError
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"message"
:
u"No course '{course_id}' found for enrollment"
.
format
(
course_id
=
course_id
)
}
)
# 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
=
request
.
user
,
ip_address
=
get_ip
(
request
),
url
=
request
.
path
)
if
redirect_url
:
return
Response
(
status
=
status
.
HTTP_403_FORBIDDEN
,
data
=
{
"message"
:
(
u"Users from this location cannot access the course '{course_id}'."
)
.
format
(
course_id
=
course_id
),
"user_message_url"
:
redirect_url
}
)
try
:
return
Response
(
api
.
add_enrollment
(
user
,
unicode
(
course_id
)))
except
CourseModeNotFoundError
as
error
:
except
CourseModeNotFoundError
as
error
:
return
Response
(
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
status
=
status
.
HTTP_400_BAD_REQUEST
,
...
@@ -305,10 +338,3 @@ class EnrollmentListView(APIView):
...
@@ -305,10 +338,3 @@ class EnrollmentListView(APIView):
)
.
format
(
user
=
user
,
course_id
=
course_id
)
)
.
format
(
user
=
user
,
course_id
=
course_id
)
}
}
)
)
except
InvalidKeyError
:
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
"message"
:
u"No course '{course_id}' found for enrollment"
.
format
(
course_id
=
course_id
)
}
)
common/djangoapps/student/tests/test_enrollment.py
View file @
70ec4c5f
...
@@ -5,18 +5,19 @@ import ddt
...
@@ -5,18 +5,19 @@ import ddt
import
unittest
import
unittest
from
mock
import
patch
from
mock
import
patch
from
django.test.utils
import
override_settings
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
util.testing
import
UrlResetMixin
from
embargo.test_utils
import
restrict_course
from
student.tests.factories
import
UserFactory
,
CourseModeFactory
from
student.tests.factories
import
UserFactory
,
CourseModeFactory
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
@ddt.ddt
@ddt.ddt
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
class
EnrollmentTest
(
ModuleStoreTestCase
):
class
EnrollmentTest
(
UrlResetMixin
,
ModuleStoreTestCase
):
"""
"""
Test student enrollment, especially with different course modes.
Test student enrollment, especially with different course modes.
"""
"""
...
@@ -25,9 +26,10 @@ class EnrollmentTest(ModuleStoreTestCase):
...
@@ -25,9 +26,10 @@ class EnrollmentTest(ModuleStoreTestCase):
EMAIL
=
"bob@example.com"
EMAIL
=
"bob@example.com"
PASSWORD
=
"edx"
PASSWORD
=
"edx"
@patch.dict
(
settings
.
FEATURES
,
{
'ENABLE_COUNTRY_ACCESS'
:
True
})
def
setUp
(
self
):
def
setUp
(
self
):
""" Create a course and user, then log in. """
""" Create a course and user, then log in. """
super
(
EnrollmentTest
,
self
)
.
setUp
()
super
(
EnrollmentTest
,
self
)
.
setUp
(
'embargo'
)
self
.
course
=
CourseFactory
.
create
()
self
.
course
=
CourseFactory
.
create
()
self
.
user
=
UserFactory
.
create
(
username
=
self
.
USERNAME
,
email
=
self
.
EMAIL
,
password
=
self
.
PASSWORD
)
self
.
user
=
UserFactory
.
create
(
username
=
self
.
USERNAME
,
email
=
self
.
EMAIL
,
password
=
self
.
PASSWORD
)
self
.
client
.
login
(
username
=
self
.
USERNAME
,
password
=
self
.
PASSWORD
)
self
.
client
.
login
(
username
=
self
.
USERNAME
,
password
=
self
.
PASSWORD
)
...
@@ -132,6 +134,29 @@ class EnrollmentTest(ModuleStoreTestCase):
...
@@ -132,6 +134,29 @@ class EnrollmentTest(ModuleStoreTestCase):
else
:
else
:
self
.
assertFalse
(
mock_update_email_opt_in
.
called
)
self
.
assertFalse
(
mock_update_email_opt_in
.
called
)
@patch.dict
(
settings
.
FEATURES
,
{
'ENABLE_COUNTRY_ACCESS'
:
True
})
def
test_embargo_restrict
(
self
):
# When accessing the course from an embargoed country,
# we should be blocked.
with
restrict_course
(
self
.
course
.
id
)
as
redirect_url
:
response
=
self
.
_change_enrollment
(
'enroll'
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
content
,
redirect_url
)
# Verify that we weren't enrolled
is_enrolled
=
CourseEnrollment
.
is_enrolled
(
self
.
user
,
self
.
course
.
id
)
self
.
assertFalse
(
is_enrolled
)
@patch.dict
(
settings
.
FEATURES
,
{
'ENABLE_COUNTRY_ACCESS'
:
True
})
def
test_embargo_allow
(
self
):
response
=
self
.
_change_enrollment
(
'enroll'
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
content
,
''
)
# Verify that we were enrolled
is_enrolled
=
CourseEnrollment
.
is_enrolled
(
self
.
user
,
self
.
course
.
id
)
self
.
assertTrue
(
is_enrolled
)
def
test_user_not_authenticated
(
self
):
def
test_user_not_authenticated
(
self
):
# Log out, so we're no longer authenticated
# Log out, so we're no longer authenticated
self
.
client
.
logout
()
self
.
client
.
logout
()
...
...
common/djangoapps/student/views.py
View file @
70ec4c5f
...
@@ -9,6 +9,7 @@ import time
...
@@ -9,6 +9,7 @@ import time
import
json
import
json
from
collections
import
defaultdict
from
collections
import
defaultdict
from
pytz
import
UTC
from
pytz
import
UTC
from
ipware.ip
import
get_ip
from
django.conf
import
settings
from
django.conf
import
settings
from
django.contrib.auth
import
logout
,
authenticate
,
login
from
django.contrib.auth
import
logout
,
authenticate
,
login
...
@@ -113,6 +114,8 @@ from xmodule.error_module import ErrorDescriptor
...
@@ -113,6 +114,8 @@ from xmodule.error_module import ErrorDescriptor
from
shoppingcart.models
import
DonationConfiguration
,
CourseRegistrationCode
from
shoppingcart.models
import
DonationConfiguration
,
CourseRegistrationCode
from
openedx.core.djangoapps.user_api.api
import
profile
as
profile_api
from
openedx.core.djangoapps.user_api.api
import
profile
as
profile_api
from
embargo
import
api
as
embargo_api
import
analytics
import
analytics
from
eventtracking
import
tracker
from
eventtracking
import
tracker
...
@@ -901,6 +904,17 @@ def change_enrollment(request, check_access=True):
...
@@ -901,6 +904,17 @@ def change_enrollment(request, check_access=True):
available_modes
=
CourseMode
.
modes_for_course_dict
(
course_id
)
available_modes
=
CourseMode
.
modes_for_course_dict
(
course_id
)
# Check whether the user is blocked from enrolling in this course
# This can occur if the user's IP is on a global blacklist
# or if the user is enrolling in a country in which the course
# is not available.
redirect_url
=
embargo_api
.
redirect_if_blocked
(
course_id
,
user
=
user
,
ip_address
=
get_ip
(
request
),
url
=
request
.
path
)
if
redirect_url
:
return
HttpResponse
(
redirect_url
)
# Check that auto enrollment is allowed for this course
# Check that auto enrollment is allowed for this course
# (= the course is NOT behind a paywall)
# (= the course is NOT behind a paywall)
if
CourseMode
.
can_auto_enroll
(
course_id
):
if
CourseMode
.
can_auto_enroll
(
course_id
):
...
...
lms/djangoapps/shoppingcart/tests/test_views.py
View file @
70ec4c5f
...
@@ -24,12 +24,11 @@ from datetime import datetime, timedelta
...
@@ -24,12 +24,11 @@ from datetime import datetime, timedelta
from
mock
import
patch
,
Mock
from
mock
import
patch
,
Mock
import
ddt
import
ddt
from
xmodule.modulestore.tests.django_utils
import
(
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
ModuleStoreTestCase
,
mixed_store_config
)
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
student.roles
import
CourseSalesAdminRole
from
student.roles
import
CourseSalesAdminRole
from
util.date_utils
import
get_default_time_display
from
util.date_utils
import
get_default_time_display
from
util.testing
import
UrlResetMixin
from
shoppingcart.views
import
_can_download_report
,
_get_date_from_str
from
shoppingcart.views
import
_can_download_report
,
_get_date_from_str
from
shoppingcart.models
import
(
from
shoppingcart.models
import
(
...
@@ -42,6 +41,7 @@ from courseware.tests.factories import InstructorFactory
...
@@ -42,6 +41,7 @@ from courseware.tests.factories import InstructorFactory
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
from
course_modes.models
import
CourseMode
from
course_modes.models
import
CourseMode
from
edxmako.shortcuts
import
render_to_response
from
edxmako.shortcuts
import
render_to_response
from
embargo.test_utils
import
restrict_course
from
shoppingcart.processors
import
render_purchase_form_html
from
shoppingcart.processors
import
render_purchase_form_html
from
shoppingcart.admin
import
SoftDeleteCouponAdmin
from
shoppingcart.admin
import
SoftDeleteCouponAdmin
from
shoppingcart.views
import
initialize_report
from
shoppingcart.views
import
initialize_report
...
@@ -1579,6 +1579,51 @@ class RegistrationCodeRedemptionCourseEnrollment(ModuleStoreTestCase):
...
@@ -1579,6 +1579,51 @@ class RegistrationCodeRedemptionCourseEnrollment(ModuleStoreTestCase):
@ddt.ddt
@ddt.ddt
class
RedeemCodeEmbargoTests
(
UrlResetMixin
,
ModuleStoreTestCase
):
"""Test blocking redeem code redemption based on country access rules. """
USERNAME
=
'bob'
PASSWORD
=
'test'
@patch.dict
(
settings
.
FEATURES
,
{
'ENABLE_COUNTRY_ACCESS'
:
True
})
def
setUp
(
self
):
super
(
RedeemCodeEmbargoTests
,
self
)
.
setUp
(
'embargo'
)
self
.
course
=
CourseFactory
.
create
()
self
.
user
=
UserFactory
.
create
(
username
=
self
.
USERNAME
,
password
=
self
.
PASSWORD
)
result
=
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
self
.
PASSWORD
)
self
.
assertTrue
(
result
,
msg
=
"Could not log in"
)
@ddt.data
(
'get'
,
'post'
)
@patch.dict
(
settings
.
FEATURES
,
{
'ENABLE_COUNTRY_ACCESS'
:
True
})
def
test_registration_code_redemption_embargo
(
self
,
method
):
# Create a valid registration code
reg_code
=
CourseRegistrationCode
.
objects
.
create
(
code
=
"abcd1234"
,
course_id
=
self
.
course
.
id
,
created_by
=
self
.
user
)
# Try to redeem the code from a restricted country
with
restrict_course
(
self
.
course
.
id
)
as
redirect_url
:
url
=
reverse
(
'register_code_redemption'
,
kwargs
=
{
'registration_code'
:
'abcd1234'
}
)
response
=
getattr
(
self
.
client
,
method
)(
url
)
self
.
assertRedirects
(
response
,
redirect_url
)
# The registration code should NOT be redeemed
is_redeemed
=
RegistrationCodeRedemption
.
objects
.
filter
(
registration_code
=
reg_code
)
.
exists
()
self
.
assertFalse
(
is_redeemed
)
# The user should NOT be enrolled
is_enrolled
=
CourseEnrollment
.
is_enrolled
(
self
.
user
,
self
.
course
.
id
)
self
.
assertFalse
(
is_enrolled
)
@ddt.ddt
class
DonationViewTest
(
ModuleStoreTestCase
):
class
DonationViewTest
(
ModuleStoreTestCase
):
"""Tests for making a donation.
"""Tests for making a donation.
...
...
lms/djangoapps/shoppingcart/views.py
View file @
70ec4c5f
...
@@ -2,9 +2,11 @@ import logging
...
@@ -2,9 +2,11 @@ import logging
import
datetime
import
datetime
import
decimal
import
decimal
import
pytz
import
pytz
from
ipware.ip
import
get_ip
from
django.db.models
import
Q
from
django.db.models
import
Q
from
django.conf
import
settings
from
django.conf
import
settings
from
django.contrib.auth.models
import
Group
from
django.contrib.auth.models
import
Group
from
django.shortcuts
import
redirect
from
django.http
import
(
from
django.http
import
(
HttpResponse
,
HttpResponseRedirect
,
HttpResponseNotFound
,
HttpResponse
,
HttpResponseRedirect
,
HttpResponseNotFound
,
HttpResponseBadRequest
,
HttpResponseForbidden
,
Http404
HttpResponseBadRequest
,
HttpResponseForbidden
,
Http404
...
@@ -28,6 +30,7 @@ from config_models.decorators import require_config
...
@@ -28,6 +30,7 @@ from config_models.decorators import require_config
from
shoppingcart.reports
import
RefundReport
,
ItemizedPurchaseReport
,
UniversityRevenueShareReport
,
CertificateStatusReport
from
shoppingcart.reports
import
RefundReport
,
ItemizedPurchaseReport
,
UniversityRevenueShareReport
,
CertificateStatusReport
from
student.models
import
CourseEnrollment
,
EnrollmentClosedError
,
CourseFullError
,
\
from
student.models
import
CourseEnrollment
,
EnrollmentClosedError
,
CourseFullError
,
\
AlreadyEnrolledError
AlreadyEnrolledError
from
embargo
import
api
as
embargo_api
from
.exceptions
import
(
from
.exceptions
import
(
ItemAlreadyInCartException
,
AlreadyEnrolledInCourseException
,
ItemAlreadyInCartException
,
AlreadyEnrolledInCourseException
,
CourseDoesNotExistException
,
ReportTypeDoesNotExistException
,
CourseDoesNotExistException
,
ReportTypeDoesNotExistException
,
...
@@ -50,6 +53,7 @@ import json
...
@@ -50,6 +53,7 @@ import json
from
xmodule_django.models
import
CourseKeyField
from
xmodule_django.models
import
CourseKeyField
from
.decorators
import
enforce_shopping_cart_enabled
from
.decorators
import
enforce_shopping_cart_enabled
log
=
logging
.
getLogger
(
"shoppingcart"
)
log
=
logging
.
getLogger
(
"shoppingcart"
)
AUDIT_LOG
=
logging
.
getLogger
(
"audit"
)
AUDIT_LOG
=
logging
.
getLogger
(
"audit"
)
...
@@ -352,6 +356,15 @@ def register_code_redemption(request, registration_code):
...
@@ -352,6 +356,15 @@ def register_code_redemption(request, registration_code):
reg_code_is_valid
,
reg_code_already_redeemed
,
course_registration
=
get_reg_code_validity
(
registration_code
,
reg_code_is_valid
,
reg_code_already_redeemed
,
course_registration
=
get_reg_code_validity
(
registration_code
,
request
,
limiter
)
request
,
limiter
)
course
=
get_course_by_id
(
getattr
(
course_registration
,
'course_id'
),
depth
=
0
)
course
=
get_course_by_id
(
getattr
(
course_registration
,
'course_id'
),
depth
=
0
)
# Restrict the user from enrolling based on country access rules
embargo_redirect
=
embargo_api
.
redirect_if_blocked
(
course
.
id
,
user
=
request
.
user
,
ip_address
=
get_ip
(
request
),
url
=
request
.
path
)
if
embargo_redirect
is
not
None
:
return
redirect
(
embargo_redirect
)
context
=
{
context
=
{
'reg_code_already_redeemed'
:
reg_code_already_redeemed
,
'reg_code_already_redeemed'
:
reg_code_already_redeemed
,
'reg_code_is_valid'
:
reg_code_is_valid
,
'reg_code_is_valid'
:
reg_code_is_valid
,
...
@@ -365,6 +378,15 @@ def register_code_redemption(request, registration_code):
...
@@ -365,6 +378,15 @@ def register_code_redemption(request, registration_code):
reg_code_is_valid
,
reg_code_already_redeemed
,
course_registration
=
get_reg_code_validity
(
registration_code
,
reg_code_is_valid
,
reg_code_already_redeemed
,
course_registration
=
get_reg_code_validity
(
registration_code
,
request
,
limiter
)
request
,
limiter
)
course
=
get_course_by_id
(
getattr
(
course_registration
,
'course_id'
),
depth
=
0
)
course
=
get_course_by_id
(
getattr
(
course_registration
,
'course_id'
),
depth
=
0
)
# Restrict the user from enrolling based on country access rules
embargo_redirect
=
embargo_api
.
redirect_if_blocked
(
course
.
id
,
user
=
request
.
user
,
ip_address
=
get_ip
(
request
),
url
=
request
.
path
)
if
embargo_redirect
is
not
None
:
return
redirect
(
embargo_redirect
)
context
=
{
context
=
{
'reg_code'
:
registration_code
,
'reg_code'
:
registration_code
,
'site_name'
:
site_name
,
'site_name'
:
site_name
,
...
...
lms/djangoapps/verify_student/tests/test_views.py
View file @
70ec4c5f
...
@@ -32,6 +32,8 @@ from student.models import CourseEnrollment
...
@@ -32,6 +32,8 @@ from student.models import CourseEnrollment
from
course_modes.tests.factories
import
CourseModeFactory
from
course_modes.tests.factories
import
CourseModeFactory
from
course_modes.models
import
CourseMode
from
course_modes.models
import
CourseMode
from
shoppingcart.models
import
Order
,
CertificateItem
from
shoppingcart.models
import
Order
,
CertificateItem
from
embargo.test_utils
import
restrict_course
from
util.testing
import
UrlResetMixin
from
verify_student.views
import
render_to_response
,
PayAndVerifyView
from
verify_student.views
import
render_to_response
,
PayAndVerifyView
from
verify_student.models
import
SoftwareSecurePhotoVerification
from
verify_student.models
import
SoftwareSecurePhotoVerification
from
reverification.tests.factories
import
MidcourseReverificationWindowFactory
from
reverification.tests.factories
import
MidcourseReverificationWindowFactory
...
@@ -60,7 +62,7 @@ class StartView(TestCase):
...
@@ -60,7 +62,7 @@ class StartView(TestCase):
@ddt.ddt
@ddt.ddt
class
TestPayAndVerifyView
(
ModuleStoreTestCase
):
class
TestPayAndVerifyView
(
UrlResetMixin
,
ModuleStoreTestCase
):
"""
"""
Tests for the payment and verification flow views.
Tests for the payment and verification flow views.
"""
"""
...
@@ -72,8 +74,9 @@ class TestPayAndVerifyView(ModuleStoreTestCase):
...
@@ -72,8 +74,9 @@ class TestPayAndVerifyView(ModuleStoreTestCase):
YESTERDAY
=
NOW
-
timedelta
(
days
=
1
)
YESTERDAY
=
NOW
-
timedelta
(
days
=
1
)
TOMORROW
=
NOW
+
timedelta
(
days
=
1
)
TOMORROW
=
NOW
+
timedelta
(
days
=
1
)
@mock.patch.dict
(
settings
.
FEATURES
,
{
'ENABLE_COUNTRY_ACCESS'
:
True
})
def
setUp
(
self
):
def
setUp
(
self
):
super
(
TestPayAndVerifyView
,
self
)
.
setUp
()
super
(
TestPayAndVerifyView
,
self
)
.
setUp
(
'embargo'
)
self
.
user
=
UserFactory
.
create
(
username
=
self
.
USERNAME
,
password
=
self
.
PASSWORD
)
self
.
user
=
UserFactory
.
create
(
username
=
self
.
USERNAME
,
password
=
self
.
PASSWORD
)
result
=
self
.
client
.
login
(
username
=
self
.
USERNAME
,
password
=
self
.
PASSWORD
)
result
=
self
.
client
.
login
(
username
=
self
.
USERNAME
,
password
=
self
.
PASSWORD
)
self
.
assertTrue
(
result
,
msg
=
"Could not log in"
)
self
.
assertTrue
(
result
,
msg
=
"Could not log in"
)
...
@@ -622,6 +625,20 @@ class TestPayAndVerifyView(ModuleStoreTestCase):
...
@@ -622,6 +625,20 @@ class TestPayAndVerifyView(ModuleStoreTestCase):
self
.
assertContains
(
response
,
"verification deadline"
)
self
.
assertContains
(
response
,
"verification deadline"
)
self
.
assertContains
(
response
,
"Jan 02, 1999 at 00:00 UTC"
)
self
.
assertContains
(
response
,
"Jan 02, 1999 at 00:00 UTC"
)
@mock.patch.dict
(
settings
.
FEATURES
,
{
'ENABLE_COUNTRY_ACCESS'
:
True
})
def
test_embargo_restrict
(
self
):
course
=
self
.
_create_course
(
"verified"
)
with
restrict_course
(
course
.
id
)
as
redirect_url
:
# Simulate that we're embargoed from accessing this
# course based on our IP address.
response
=
self
.
_get_page
(
'verify_student_start_flow'
,
course
.
id
,
expected_status_code
=
302
)
self
.
assertRedirects
(
response
,
redirect_url
)
@mock.patch.dict
(
settings
.
FEATURES
,
{
'ENABLE_COUNTRY_ACCESS'
:
True
})
def
test_embargo_allow
(
self
):
course
=
self
.
_create_course
(
"verified"
)
self
.
_get_page
(
'verify_student_start_flow'
,
course
.
id
)
def
_create_course
(
self
,
*
course_modes
,
**
kwargs
):
def
_create_course
(
self
,
*
course_modes
,
**
kwargs
):
"""Create a new course with the specified course modes. """
"""Create a new course with the specified course modes. """
course
=
CourseFactory
.
create
()
course
=
CourseFactory
.
create
()
...
...
lms/djangoapps/verify_student/views.py
View file @
70ec4c5f
...
@@ -8,6 +8,7 @@ import decimal
...
@@ -8,6 +8,7 @@ import decimal
import
datetime
import
datetime
from
collections
import
namedtuple
from
collections
import
namedtuple
from
pytz
import
UTC
from
pytz
import
UTC
from
ipware.ip
import
get_ip
from
edxmako.shortcuts
import
render_to_response
,
render_to_string
from
edxmako.shortcuts
import
render_to_response
,
render_to_string
...
@@ -46,6 +47,8 @@ from .exceptions import WindowExpiredException
...
@@ -46,6 +47,8 @@ from .exceptions import WindowExpiredException
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
microsite_configuration
import
microsite
from
microsite_configuration
import
microsite
from
embargo
import
api
as
embargo_api
from
util.json_request
import
JsonResponse
from
util.json_request
import
JsonResponse
from
util.date_utils
import
get_default_time_display
from
util.date_utils
import
get_default_time_display
...
@@ -256,6 +259,17 @@ class PayAndVerifyView(View):
...
@@ -256,6 +259,17 @@ class PayAndVerifyView(View):
log
.
warn
(
u"No course specified for verification flow request."
)
log
.
warn
(
u"No course specified for verification flow request."
)
raise
Http404
raise
Http404
# Check whether the user has access to this course
# based on country access rules.
redirect_url
=
embargo_api
.
redirect_if_blocked
(
course_key
,
user
=
request
.
user
,
ip_address
=
get_ip
(
request
),
url
=
request
.
path
)
if
redirect_url
:
return
redirect
(
redirect_url
)
# Check that the course has an unexpired verified mode
# Check that the course has an unexpired verified mode
course_mode
,
expired_course_mode
=
self
.
_get_verified_modes_for_course
(
course_key
)
course_mode
,
expired_course_mode
=
self
.
_get_verified_modes_for_course
(
course_key
)
...
...
lms/static/js/spec/student_account/enrollment_spec.js
View file @
70ec4c5f
...
@@ -6,7 +6,8 @@ define(['js/common_helpers/ajax_helpers', 'js/student_account/enrollment'],
...
@@ -6,7 +6,8 @@ define(['js/common_helpers/ajax_helpers', 'js/student_account/enrollment'],
var
COURSE_KEY
=
'edX/DemoX/Fall'
,
var
COURSE_KEY
=
'edX/DemoX/Fall'
,
ENROLL_URL
=
'/api/enrollment/v1/enrollment'
,
ENROLL_URL
=
'/api/enrollment/v1/enrollment'
,
FORWARD_URL
=
'/course_modes/choose/edX/DemoX/Fall/'
;
FORWARD_URL
=
'/course_modes/choose/edX/DemoX/Fall/'
,
EMBARGO_MSG_URL
=
'/embargo/blocked-message/enrollment/default/'
;
beforeEach
(
function
()
{
beforeEach
(
function
()
{
// Mock the redirect call
// Mock the redirect call
...
@@ -49,6 +50,27 @@ define(['js/common_helpers/ajax_helpers', 'js/student_account/enrollment'],
...
@@ -49,6 +50,27 @@ define(['js/common_helpers/ajax_helpers', 'js/student_account/enrollment'],
expect
(
EnrollmentInterface
.
redirect
).
toHaveBeenCalledWith
(
FORWARD_URL
);
expect
(
EnrollmentInterface
.
redirect
).
toHaveBeenCalledWith
(
FORWARD_URL
);
});
});
it
(
'redirects the user if blocked by an embargo'
,
function
()
{
// Spy on Ajax requests
var
requests
=
AjaxHelpers
.
requests
(
this
);
// Attempt to enroll the user
EnrollmentInterface
.
enroll
(
COURSE_KEY
);
// Simulate an error response (403) from the server
// with a "user_message_url" parameter for the redirect.
// This will redirect the user to a page with messaging
// explaining why he/she can't enroll.
AjaxHelpers
.
respondWithError
(
requests
,
403
,
{
'user_message_url'
:
EMBARGO_MSG_URL
}
);
// Verify that the user was redirected
expect
(
EnrollmentInterface
.
redirect
).
toHaveBeenCalledWith
(
EMBARGO_MSG_URL
);
});
});
});
}
}
);
);
lms/static/js/student_account/enrollment.js
View file @
70ec4c5f
...
@@ -36,7 +36,26 @@ var edx = edx || {};
...
@@ -36,7 +36,26 @@ var edx = edx || {};
data
:
data
,
data
:
data
,
headers
:
this
.
headers
,
headers
:
this
.
headers
,
context
:
this
context
:
this
}).
always
(
function
()
{
})
.
fail
(
function
(
jqXHR
)
{
var
responseData
=
JSON
.
parse
(
jqXHR
.
responseText
);
if
(
jqXHR
.
status
===
403
&&
responseData
.
user_message_url
)
{
// Check if we've been blocked from the course
// because of country access rules.
// If so, redirect to a page explaining to the user
// why they were blocked.
this
.
redirect
(
responseData
.
user_message_url
);
}
else
{
// Otherwise, go to the track selection page as usual.
// This can occur, for example, when a course does not
// have a free enrollment mode, so we can't auto-enroll.
this
.
redirect
(
this
.
trackSelectionUrl
(
courseKey
)
);
}
})
.
done
(
function
()
{
// If we successfully enrolled, go to the track selection
// page to allow the user to choose a paid enrollment mode.
this
.
redirect
(
this
.
trackSelectionUrl
(
courseKey
)
);
this
.
redirect
(
this
.
trackSelectionUrl
(
courseKey
)
);
});
});
},
},
...
...
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