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
d54f79f5
Commit
d54f79f5
authored
8 years ago
by
Robert Raposa
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Switch dashboard from GET to POST.
parent
58856964
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
399 additions
and
234 deletions
+399
-234
lms/djangoapps/instructor/tests/test_api.py
+262
-127
lms/djangoapps/instructor/views/api.py
+82
-86
lms/static/coffee/src/instructor_dashboard/data_download.coffee
+8
-0
lms/static/coffee/src/instructor_dashboard/extensions.coffee
+4
-0
lms/static/coffee/src/instructor_dashboard/membership.coffee
+4
-0
lms/static/coffee/src/instructor_dashboard/send_email.coffee
+2
-0
lms/static/coffee/src/instructor_dashboard/student_admin.coffee
+12
-0
lms/static/coffee/src/instructor_dashboard/util.coffee
+2
-0
lms/static/js/instructor_dashboard/ecommerce.js
+2
-0
lms/static/js/spec/instructor_dashboard/student_admin_spec.js
+17
-17
lms/static/js/spec/staff_debug_actions_spec.js
+3
-3
lms/static/js/staff_debug_actions.js
+1
-1
No files found.
lms/djangoapps/instructor/tests/test_api.py
View file @
d54f79f5
...
...
@@ -17,7 +17,7 @@ from django.conf import settings
from
django.contrib.auth.models
import
User
from
django.core
import
mail
from
django.core.files.uploadedfile
import
SimpleUploadedFile
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
as
django_reverse
from
django.http
import
HttpRequest
,
HttpResponse
from
django.test
import
RequestFactory
,
TestCase
from
django.test.utils
import
override_settings
...
...
@@ -34,7 +34,9 @@ from xmodule.modulestore import ModuleStoreEnum
from
bulk_email.models
import
BulkEmailFlag
from
course_modes.models
import
CourseMode
from
courseware.models
import
StudentModule
from
courseware.tests.factories
import
StaffFactory
,
InstructorFactory
,
BetaTesterFactory
,
UserProfileFactory
from
courseware.tests.factories
import
(
BetaTesterFactory
,
GlobalStaffFactory
,
InstructorFactory
,
StaffFactory
,
UserProfileFactory
)
from
courseware.tests.helpers
import
LoginEnrollmentTestCase
from
django_comment_common.models
import
FORUM_ROLE_COMMUNITY_TA
from
django_comment_common.utils
import
seed_permissions_roles
...
...
@@ -131,6 +133,82 @@ EXECUTIVE_SUMMARY_DATA = (
)
INSTRUCTOR_GET_ENDPOINTS
=
set
([
'get_anon_ids'
,
'get_coupon_codes'
,
'get_issued_certificates'
,
'get_sale_order_records'
,
'get_sale_records'
,
])
INSTRUCTOR_POST_ENDPOINTS
=
set
([
'active_registration_codes'
,
'add_users_to_cohorts'
,
'bulk_beta_modify_access'
,
'calculate_grades_csv'
,
'change_due_date'
,
'export_ora2_data'
,
'generate_registration_codes'
,
'get_enrollment_report'
,
'get_exec_summary_report'
,
'get_grading_config'
,
'get_problem_responses'
,
'get_proctored_exam_results'
,
'get_registration_codes'
,
'get_student_progress_url'
,
'get_students_features'
,
'get_students_who_may_enroll'
,
'get_user_invoice_preference'
,
'list_background_email_tasks'
,
'list_course_role_members'
,
'list_email_content'
,
'list_entrance_exam_instructor_tasks'
,
'list_financial_report_downloads'
,
'list_forum_members'
,
'list_instructor_tasks'
,
'list_report_downloads'
,
'mark_student_can_skip_entrance_exam'
,
'modify_access'
,
'register_and_enroll_students'
,
'rescore_entrance_exam'
,
'rescore_problem'
,
'reset_due_date'
,
'reset_student_attempts'
,
'reset_student_attempts_for_entrance_exam'
,
'sale_validation'
,
'show_student_extensions'
,
'show_unit_extensions'
,
'send_email'
,
'spent_registration_codes'
,
'students_update_enrollment'
,
'update_forum_role_membership'
,
])
def
reverse
(
endpoint
,
args
=
None
,
kwargs
=
None
,
is_dashboard_endpoint
=
True
):
"""
Simple wrapper of Django's reverse that first ensures that we have declared
each endpoint under test.
Arguments:
args: The args to be passed through to reverse.
endpoint: The endpoint to be passed through to reverse.
kwargs: The kwargs to be passed through to reverse.
is_dashboard_endpoint: True if this is an instructor dashboard endpoint
that must be declared in the INSTRUCTOR_GET_ENDPOINTS or
INSTRUCTOR_GET_ENDPOINTS sets, or false otherwise.
Returns:
The return of Django's reverse function
"""
is_endpoint_declared
=
endpoint
in
INSTRUCTOR_GET_ENDPOINTS
or
endpoint
in
INSTRUCTOR_POST_ENDPOINTS
if
is_dashboard_endpoint
and
is_endpoint_declared
is
False
:
# Verify that all endpoints are declared so we can ensure they are
# properly validated elsewhere.
raise
ValueError
(
"The endpoint {} must be declared in ENDPOINTS before use."
.
format
(
endpoint
))
return
django_reverse
(
endpoint
,
args
=
args
,
kwargs
=
kwargs
)
@common_exceptions_400
def
view_success
(
request
):
# pylint: disable=unused-argument
"A dummy view for testing that returns a simple HTTP response"
...
...
@@ -192,6 +270,61 @@ class TestCommonExceptions400(TestCase):
@attr
(
'shard_1'
)
@ddt.ddt
class
TestEndpointHttpMethods
(
SharedModuleStoreTestCase
,
LoginEnrollmentTestCase
):
"""
Ensure that users can make GET requests against endpoints that allow GET,
and not against those that don't allow GET.
"""
@classmethod
def
setUpClass
(
cls
):
"""
Set up test course.
"""
super
(
TestEndpointHttpMethods
,
cls
)
.
setUpClass
()
cls
.
course
=
CourseFactory
.
create
()
def
setUp
(
self
):
"""
Set up global staff role so authorization will not fail.
"""
super
(
TestEndpointHttpMethods
,
self
)
.
setUp
()
global_user
=
GlobalStaffFactory
()
self
.
client
.
login
(
username
=
global_user
.
username
,
password
=
'test'
)
@ddt.data
(
*
INSTRUCTOR_POST_ENDPOINTS
)
def
test_endpoints_reject_get
(
self
,
data
):
"""
Tests that POST endpoints are rejected with 405 when using GET.
"""
url
=
reverse
(
data
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
get
(
url
)
self
.
assertEqual
(
response
.
status_code
,
405
,
"Endpoint {} returned status code {} instead of a 405. It should not allow GET."
.
format
(
data
,
response
.
status_code
)
)
@ddt.data
(
*
INSTRUCTOR_GET_ENDPOINTS
)
def
test_endpoints_accept_get
(
self
,
data
):
"""
Tests that GET endpoints are not rejected with 405 when using GET.
"""
url
=
reverse
(
data
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
get
(
url
)
self
.
assertNotEqual
(
response
.
status_code
,
405
,
"Endpoint {} returned status code 405 where it shouldn't, since it should allow GET."
.
format
(
data
)
)
@attr
(
'shard_1'
)
@patch
(
'bulk_email.models.html_to_text'
,
Mock
(
return_value
=
'Mocking CourseEmail.text_message'
,
autospec
=
True
))
class
TestInstructorAPIDenyLevels
(
SharedModuleStoreTestCase
,
LoginEnrollmentTestCase
):
"""
...
...
@@ -271,10 +404,10 @@ class TestInstructorAPIDenyLevels(SharedModuleStoreTestCase, LoginEnrollmentTest
msg: message to display if assertion fails.
"""
url
=
reverse
(
endpoint
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
if
endpoint
in
[
'send_email'
,
'students_update_enrollment'
,
'bulk_beta_modify_access'
]:
response
=
self
.
client
.
post
(
url
,
args
)
else
:
if
endpoint
in
INSTRUCTOR_GET_ENDPOINTS
:
response
=
self
.
client
.
get
(
url
,
args
)
else
:
response
=
self
.
client
.
post
(
url
,
args
)
self
.
assertEqual
(
response
.
status_code
,
status_code
,
...
...
@@ -1919,13 +2052,13 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe
def
test_modify_access_noparams
(
self
):
""" Test missing all query parameters. """
url
=
reverse
(
'modify_access'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
)
response
=
self
.
client
.
pos
t
(
url
)
self
.
assertEqual
(
response
.
status_code
,
400
)
def
test_modify_access_bad_action
(
self
):
""" Test with an invalid action parameter. """
url
=
reverse
(
'modify_access'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
other_staff
.
email
,
'rolename'
:
'staff'
,
'action'
:
'robot-not-an-action'
,
...
...
@@ -1935,7 +2068,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe
def
test_modify_access_bad_role
(
self
):
""" Test with an invalid action parameter. """
url
=
reverse
(
'modify_access'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
other_staff
.
email
,
'rolename'
:
'robot-not-a-roll'
,
'action'
:
'revoke'
,
...
...
@@ -1944,7 +2077,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe
def
test_modify_access_allow
(
self
):
url
=
reverse
(
'modify_access'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
other_user
.
email
,
'rolename'
:
'staff'
,
'action'
:
'allow'
,
...
...
@@ -1953,7 +2086,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe
def
test_modify_access_allow_with_uname
(
self
):
url
=
reverse
(
'modify_access'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
other_instructor
.
username
,
'rolename'
:
'staff'
,
'action'
:
'allow'
,
...
...
@@ -1962,7 +2095,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe
def
test_modify_access_revoke
(
self
):
url
=
reverse
(
'modify_access'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
other_staff
.
email
,
'rolename'
:
'staff'
,
'action'
:
'revoke'
,
...
...
@@ -1971,7 +2104,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe
def
test_modify_access_revoke_with_username
(
self
):
url
=
reverse
(
'modify_access'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
other_staff
.
username
,
'rolename'
:
'staff'
,
'action'
:
'revoke'
,
...
...
@@ -1980,7 +2113,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe
def
test_modify_access_with_fake_user
(
self
):
url
=
reverse
(
'modify_access'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
'GandalfTheGrey'
,
'rolename'
:
'staff'
,
'action'
:
'revoke'
,
...
...
@@ -1997,7 +2130,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe
self
.
other_user
.
is_active
=
False
self
.
other_user
.
save
()
# pylint: disable=no-member
url
=
reverse
(
'modify_access'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
other_user
.
username
,
'rolename'
:
'beta'
,
'action'
:
'allow'
,
...
...
@@ -2013,7 +2146,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe
def
test_modify_access_revoke_not_allowed
(
self
):
""" Test revoking access that a user does not have. """
url
=
reverse
(
'modify_access'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
other_staff
.
email
,
'rolename'
:
'instructor'
,
'action'
:
'revoke'
,
...
...
@@ -2025,7 +2158,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe
Test that an instructor cannot remove instructor privelages from themself.
"""
url
=
reverse
(
'modify_access'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
instructor
.
email
,
'rolename'
:
'instructor'
,
'action'
:
'revoke'
,
...
...
@@ -2044,20 +2177,20 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe
def
test_list_course_role_members_noparams
(
self
):
""" Test missing all query parameters. """
url
=
reverse
(
'list_course_role_members'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
)
response
=
self
.
client
.
pos
t
(
url
)
self
.
assertEqual
(
response
.
status_code
,
400
)
def
test_list_course_role_members_bad_rolename
(
self
):
""" Test with an invalid rolename parameter. """
url
=
reverse
(
'list_course_role_members'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'rolename'
:
'robot-not-a-rolename'
,
})
self
.
assertEqual
(
response
.
status_code
,
400
)
def
test_list_course_role_members_staff
(
self
):
url
=
reverse
(
'list_course_role_members'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'rolename'
:
'staff'
,
})
self
.
assertEqual
(
response
.
status_code
,
200
)
...
...
@@ -2079,7 +2212,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe
def
test_list_course_role_members_beta
(
self
):
url
=
reverse
(
'list_course_role_members'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'rolename'
:
'beta'
,
})
self
.
assertEqual
(
response
.
status_code
,
200
)
...
...
@@ -2112,7 +2245,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe
Get unique_student_identifier, rolename and action and update forum role.
"""
url
=
reverse
(
'update_forum_role_membership'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
identifier
,
...
...
@@ -2185,7 +2318,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
"""
enroll user using a registration code
"""
redeem_url
=
reverse
(
'
register_code_redemption'
,
args
=
[
code
]
)
redeem_url
=
reverse
(
'
shoppingcart.views.register_code_redemption'
,
args
=
[
code
],
is_dashboard_endpoint
=
False
)
self
.
client
.
login
(
username
=
user
.
username
,
password
=
'test'
)
response
=
self
.
client
.
get
(
redeem_url
)
self
.
assertEquals
(
response
.
status_code
,
200
)
...
...
@@ -2265,10 +2398,16 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
mode_slug
=
CourseMode
.
HONOR
)
# update the quantity of the cart item paid_course_reg_item
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.update_user_cart'
),
{
'ItemId'
:
paid_course_reg_item
.
id
,
'qty'
:
'4'
})
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.update_user_cart'
,
is_dashboard_endpoint
=
False
),
{
'ItemId'
:
paid_course_reg_item
.
id
,
'qty'
:
'4'
}
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
# apply the coupon code to the item in the cart
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
),
{
'code'
:
coupon
.
code
})
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
,
is_dashboard_endpoint
=
False
),
{
'code'
:
coupon
.
code
}
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
cart
.
purchase
()
# get the updated item
...
...
@@ -2277,7 +2416,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
coupon_redemption
=
CouponRedemption
.
objects
.
select_related
(
'coupon'
)
.
filter
(
order
=
self
.
cart
)
sale_order_url
=
reverse
(
'get_sale_order_records'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
sale_order_url
)
response
=
self
.
client
.
pos
t
(
sale_order_url
)
self
.
assertEqual
(
response
[
'Content-Type'
],
'text/csv'
)
self
.
assertIn
(
'36'
,
response
.
content
.
split
(
'
\r\n
'
)[
1
])
self
.
assertIn
(
str
(
item
.
unit_cost
),
response
.
content
.
split
(
'
\r\n
'
)[
1
],)
...
...
@@ -2301,11 +2440,18 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
PaidCourseRegistration
.
add_to_order
(
self
.
cart
,
self
.
course
.
id
)
# apply the coupon code to the item in the cart
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
),
{
'code'
:
coupon
.
code
})
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
,
is_dashboard_endpoint
=
False
),
{
'code'
:
coupon
.
code
}
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
# URL for instructor dashboard
instructor_dashboard
=
reverse
(
'instructor_dashboard'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
instructor_dashboard
=
reverse
(
'instructor_dashboard'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()},
is_dashboard_endpoint
=
False
)
# visit the instructor dashboard page and
# check that the coupon redeem count should be 0
resp
=
self
.
client
.
get
(
instructor_dashboard
)
...
...
@@ -2342,7 +2488,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
'get_sale_records'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()}
)
response
=
self
.
client
.
ge
t
(
url
+
'/csv'
,
{})
response
=
self
.
client
.
pos
t
(
url
+
'/csv'
,
{})
self
.
assertEqual
(
response
[
'Content-Type'
],
'text/csv'
)
def
test_get_sale_records_features_json
(
self
):
...
...
@@ -2361,7 +2507,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
course_registration_code
.
save
()
url
=
reverse
(
'get_sale_records'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertIn
(
'sale'
,
res_json
)
...
...
@@ -2411,7 +2557,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
course_registration_code
.
save
()
url
=
reverse
(
'get_sale_records'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertIn
(
'sale'
,
res_json
)
...
...
@@ -2459,7 +2605,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
)
problem_location
=
''
response
=
self
.
client
.
ge
t
(
url
,
{
'problem_location'
:
problem_location
})
response
=
self
.
client
.
pos
t
(
url
,
{
'problem_location'
:
problem_location
})
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
res_json
,
'Could not find problem with this location.'
)
...
...
@@ -2494,7 +2640,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
)
problem_location
=
''
response
=
self
.
client
.
ge
t
(
url
,
{
'problem_location'
:
problem_location
})
response
=
self
.
client
.
pos
t
(
url
,
{
'problem_location'
:
problem_location
})
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertIn
(
'status'
,
res_json
)
status
=
res_json
[
'status'
]
...
...
@@ -2515,7 +2661,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
with
patch
(
'instructor_task.api.submit_calculate_problem_responses_csv'
)
as
submit_task_function
:
error
=
AlreadyRunningError
()
submit_task_function
.
side_effect
=
error
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertIn
(
'status'
,
res_json
)
self
.
assertIn
(
'already in progress'
,
res_json
[
'status'
])
...
...
@@ -2529,7 +2675,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
student
.
profile
.
city
=
"Mos Eisley {}"
.
format
(
student
.
id
)
student
.
profile
.
save
()
url
=
reverse
(
'get_students_features'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertIn
(
'students'
,
res_json
)
for
student
in
self
.
students
:
...
...
@@ -2551,7 +2697,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
url
=
reverse
(
'get_students_features'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
set_course_cohort_settings
(
self
.
course
.
id
,
is_cohorted
=
is_cohorted
)
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
'cohort'
in
res_json
[
'feature_names'
],
is_cohorted
)
...
...
@@ -2571,7 +2717,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
url
=
reverse
(
'get_students_features'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
'team'
in
res_json
[
'feature_names'
],
has_teams
)
...
...
@@ -2587,7 +2733,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)}
)
# Successful case:
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertIn
(
'status'
,
res_json
)
self
.
assertNotIn
(
'currently being created'
,
res_json
[
'status'
])
...
...
@@ -2595,7 +2741,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
with
patch
(
'instructor_task.api.submit_calculate_may_enroll_csv'
)
as
submit_task_function
:
error
=
AlreadyRunningError
()
submit_task_function
.
side_effect
=
error
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertIn
(
'status'
,
res_json
)
self
.
assertIn
(
'currently being created'
,
res_json
[
'status'
])
...
...
@@ -2610,7 +2756,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)}
)
# Successful case:
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertIn
(
'status'
,
res_json
)
self
.
assertNotIn
(
'currently being created'
,
res_json
[
'status'
])
...
...
@@ -2618,7 +2764,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
with
patch
(
'instructor_task.api.submit_proctored_exam_results_report'
)
as
submit_task_function
:
error
=
AlreadyRunningError
()
submit_task_function
.
side_effect
=
error
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertIn
(
'status'
,
res_json
)
self
.
assertIn
(
'currently being created'
,
res_json
[
'status'
])
...
...
@@ -2704,7 +2850,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
self
.
client
.
login
(
username
=
self
.
instructor
.
username
,
password
=
'test'
)
url
=
reverse
(
'get_enrollment_report'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
self
.
assertIn
(
'The detailed enrollment report is being created.'
,
response
.
content
)
def
test_bulk_purchase_detailed_report
(
self
):
...
...
@@ -2716,11 +2862,16 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
"""
paid_course_reg_item
=
PaidCourseRegistration
.
add_to_order
(
self
.
cart
,
self
.
course
.
id
)
# update the quantity of the cart item paid_course_reg_item
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.update_user_cart'
),
{
'ItemId'
:
paid_course_reg_item
.
id
,
'qty'
:
'4'
})
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.update_user_cart'
,
is_dashboard_endpoint
=
False
),
{
'ItemId'
:
paid_course_reg_item
.
id
,
'qty'
:
'4'
}
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
# apply the coupon code to the item in the cart
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
),
{
'code'
:
self
.
coupon_code
})
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
,
is_dashboard_endpoint
=
False
),
{
'code'
:
self
.
coupon_code
}
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
cart
.
purchase
()
...
...
@@ -2754,7 +2905,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
self
.
client
.
login
(
username
=
self
.
instructor
.
username
,
password
=
'test'
)
url
=
reverse
(
'get_enrollment_report'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
self
.
assertIn
(
'The detailed enrollment report is being created.'
,
response
.
content
)
def
test_create_registration_code_without_invoice_and_order
(
self
):
...
...
@@ -2776,7 +2927,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
self
.
client
.
login
(
username
=
self
.
instructor
.
username
,
password
=
'test'
)
url
=
reverse
(
'get_enrollment_report'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
self
.
assertIn
(
'The detailed enrollment report is being created.'
,
response
.
content
)
def
test_invoice_payment_is_still_pending_for_registration_codes
(
self
):
...
...
@@ -2801,7 +2952,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
self
.
client
.
login
(
username
=
self
.
instructor
.
username
,
password
=
'test'
)
url
=
reverse
(
'get_enrollment_report'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
self
.
assertIn
(
'The detailed enrollment report is being created.'
,
response
.
content
)
@patch.object
(
instructor
.
views
.
api
,
'anonymous_id_for_user'
,
Mock
(
return_value
=
'42'
))
...
...
@@ -2811,7 +2962,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
Test the CSV output for the anonymized user ids.
"""
url
=
reverse
(
'get_anon_ids'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
self
.
assertEqual
(
response
[
'Content-Type'
],
'text/csv'
)
body
=
response
.
content
.
replace
(
'
\r
'
,
''
)
self
.
assertTrue
(
body
.
startswith
(
...
...
@@ -2829,7 +2980,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
(
'mock_file_name_1'
,
'https://1.mock.url'
),
(
'mock_file_name_2'
,
'https://2.mock.url'
),
]
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
expected_response
=
{
"downloads"
:
[
...
...
@@ -2858,12 +3009,12 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
success_status
=
"The {report_type} report is being created."
.
format
(
report_type
=
report_type
)
if
report_type
==
'problem responses'
:
with
patch
(
task_api_endpoint
):
response
=
self
.
client
.
ge
t
(
url
,
{
'problem_location'
:
''
})
response
=
self
.
client
.
pos
t
(
url
,
{
'problem_location'
:
''
})
self
.
assertIn
(
success_status
,
response
.
content
)
else
:
CourseFinanceAdminRole
(
self
.
course
.
id
)
.
add_users
(
self
.
instructor
)
with
patch
(
task_api_endpoint
):
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
self
.
assertIn
(
success_status
,
response
.
content
)
@ddt.data
(
*
EXECUTIVE_SUMMARY_DATA
)
...
...
@@ -2881,7 +3032,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
CourseFinanceAdminRole
(
self
.
course
.
id
)
.
add_users
(
self
.
instructor
)
with
patch
(
task_api_endpoint
):
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
success_status
=
"The {report_type} report is being created."
\
" To view the status of the report, see Pending"
\
" Tasks below"
.
format
(
report_type
=
report_type
)
...
...
@@ -2903,7 +3054,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
CourseFinanceAdminRole
(
self
.
course
.
id
)
.
add_users
(
self
.
instructor
)
with
patch
(
task_api_endpoint
)
as
mock
:
mock
.
side_effect
=
AlreadyRunningError
()
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
already_running_status
=
"The {report_type} report is currently being created."
\
" To view the status of the report, see Pending Tasks below."
\
" You will be able to download the report"
\
...
...
@@ -2916,7 +3067,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
with
patch
(
'instructor_task.api.submit_export_ora2_data'
)
as
mock_submit_ora2_task
:
mock_submit_ora2_task
.
return_value
=
True
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
success_status
=
"The ORA data report is being generated."
self
.
assertIn
(
success_status
,
response
.
content
)
...
...
@@ -2925,17 +3076,15 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
with
patch
(
'instructor_task.api.submit_export_ora2_data'
)
as
mock_submit_ora2_task
:
mock_submit_ora2_task
.
side_effect
=
AlreadyRunningError
()
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
already_running_status
=
"An ORA data report generation task is already in progress."
self
.
assertIn
(
already_running_status
,
response
.
content
)
def
test_get_student_progress_url
(
self
):
""" Test that progress_url is in the successful response. """
url
=
reverse
(
'get_student_progress_url'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
url
+=
"?unique_student_identifier={}"
.
format
(
quote
(
self
.
students
[
0
]
.
email
.
encode
(
"utf-8"
))
)
response
=
self
.
client
.
get
(
url
)
data
=
{
'unique_student_identifier'
:
self
.
students
[
0
]
.
email
.
encode
(
"utf-8"
)}
response
=
self
.
client
.
post
(
url
,
data
)
self
.
assertEqual
(
response
.
status_code
,
200
)
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertIn
(
'progress_url'
,
res_json
)
...
...
@@ -2943,10 +3092,8 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
def
test_get_student_progress_url_from_uname
(
self
):
""" Test that progress_url is in the successful response. """
url
=
reverse
(
'get_student_progress_url'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
url
+=
"?unique_student_identifier={}"
.
format
(
quote
(
self
.
students
[
0
]
.
username
.
encode
(
"utf-8"
))
)
response
=
self
.
client
.
get
(
url
)
data
=
{
'unique_student_identifier'
:
self
.
students
[
0
]
.
username
.
encode
(
"utf-8"
)}
response
=
self
.
client
.
post
(
url
,
data
)
self
.
assertEqual
(
response
.
status_code
,
200
)
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertIn
(
'progress_url'
,
res_json
)
...
...
@@ -2954,13 +3101,13 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
def
test_get_student_progress_url_noparams
(
self
):
""" Test that the endpoint 404's without the required query params. """
url
=
reverse
(
'get_student_progress_url'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
)
response
=
self
.
client
.
pos
t
(
url
)
self
.
assertEqual
(
response
.
status_code
,
400
)
def
test_get_student_progress_url_nostudent
(
self
):
""" Test that the endpoint 400's when requesting an unknown email. """
url
=
reverse
(
'get_student_progress_url'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
)
response
=
self
.
client
.
pos
t
(
url
)
self
.
assertEqual
(
response
.
status_code
,
400
)
...
...
@@ -3001,7 +3148,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes
def
test_reset_student_attempts_deletall
(
self
):
""" Make sure no one can delete all students state on a problem. """
url
=
reverse
(
'reset_student_attempts'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'problem_to_reset'
:
self
.
problem_urlname
,
'all_students'
:
True
,
'delete_module'
:
True
,
...
...
@@ -3011,7 +3158,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes
def
test_reset_student_attempts_single
(
self
):
""" Test reset single student attempts. """
url
=
reverse
(
'reset_student_attempts'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'problem_to_reset'
:
self
.
problem_urlname
,
'unique_student_identifier'
:
self
.
student
.
email
,
})
...
...
@@ -3028,7 +3175,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes
def
test_reset_student_attempts_all
(
self
,
act
):
""" Test reset all student attempts. """
url
=
reverse
(
'reset_student_attempts'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'problem_to_reset'
:
self
.
problem_urlname
,
'all_students'
:
True
,
})
...
...
@@ -3038,7 +3185,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes
def
test_reset_student_attempts_missingmodule
(
self
):
""" Test reset for non-existant problem. """
url
=
reverse
(
'reset_student_attempts'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'problem_to_reset'
:
'robot-not-a-real-module'
,
'unique_student_identifier'
:
self
.
student
.
email
,
})
...
...
@@ -3047,7 +3194,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes
def
test_reset_student_attempts_delete
(
self
):
""" Test delete single student state. """
url
=
reverse
(
'reset_student_attempts'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'problem_to_reset'
:
self
.
problem_urlname
,
'unique_student_identifier'
:
self
.
student
.
email
,
'delete_module'
:
True
,
...
...
@@ -3066,7 +3213,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes
def
test_reset_student_attempts_nonsense
(
self
):
""" Test failure with both unique_student_identifier and all_students. """
url
=
reverse
(
'reset_student_attempts'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'problem_to_reset'
:
self
.
problem_urlname
,
'unique_student_identifier'
:
self
.
student
.
email
,
'all_students'
:
True
,
...
...
@@ -3077,7 +3224,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes
def
test_rescore_problem_single
(
self
,
act
):
""" Test rescoring of a single student. """
url
=
reverse
(
'rescore_problem'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'problem_to_reset'
:
self
.
problem_urlname
,
'unique_student_identifier'
:
self
.
student
.
email
,
})
...
...
@@ -3088,7 +3235,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes
def
test_rescore_problem_single_from_uname
(
self
,
act
):
""" Test rescoring of a single student. """
url
=
reverse
(
'rescore_problem'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'problem_to_reset'
:
self
.
problem_urlname
,
'unique_student_identifier'
:
self
.
student
.
username
,
})
...
...
@@ -3099,7 +3246,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes
def
test_rescore_problem_all
(
self
,
act
):
""" Test rescoring for all students. """
url
=
reverse
(
'rescore_problem'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'problem_to_reset'
:
self
.
problem_urlname
,
'all_students'
:
True
,
})
...
...
@@ -3111,7 +3258,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes
""" Test course has entrance exam id set while resetting attempts"""
url
=
reverse
(
'reset_student_attempts_for_entrance_exam'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'all_students'
:
True
,
'delete_module'
:
False
,
})
...
...
@@ -3121,7 +3268,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes
def
test_rescore_entrance_exam_with_invalid_exam
(
self
):
""" Test course has entrance exam id set while re-scoring. """
url
=
reverse
(
'rescore_entrance_exam'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
student
.
email
,
})
self
.
assertEqual
(
response
.
status_code
,
400
)
...
...
@@ -3225,7 +3372,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE
""" Make sure no one can delete all students state on entrance exam. """
url
=
reverse
(
'reset_student_attempts_for_entrance_exam'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'all_students'
:
True
,
'delete_module'
:
True
,
})
...
...
@@ -3235,7 +3382,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE
""" Test reset single student attempts for entrance exam. """
url
=
reverse
(
'reset_student_attempts_for_entrance_exam'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
student
.
email
,
})
self
.
assertEqual
(
response
.
status_code
,
200
)
...
...
@@ -3253,7 +3400,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE
""" Test reset all student attempts for entrance exam. """
url
=
reverse
(
'reset_student_attempts_for_entrance_exam'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'all_students'
:
True
,
})
self
.
assertEqual
(
response
.
status_code
,
200
)
...
...
@@ -3263,7 +3410,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE
""" Test reset for invalid entrance exam. """
url
=
reverse
(
'reset_student_attempts_for_entrance_exam'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course_with_invalid_ee
.
id
)})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
student
.
email
,
})
self
.
assertEqual
(
response
.
status_code
,
400
)
...
...
@@ -3272,7 +3419,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE
""" Test delete single student entrance exam state. """
url
=
reverse
(
'reset_student_attempts_for_entrance_exam'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
student
.
email
,
'delete_module'
:
True
,
})
...
...
@@ -3288,7 +3435,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE
self
.
client
.
login
(
username
=
staff_user
.
username
,
password
=
'test'
)
url
=
reverse
(
'reset_student_attempts_for_entrance_exam'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
student
.
email
,
'delete_module'
:
True
,
})
...
...
@@ -3298,7 +3445,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE
""" Test failure with both unique_student_identifier and all_students. """
url
=
reverse
(
'reset_student_attempts_for_entrance_exam'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
student
.
email
,
'all_students'
:
True
,
})
...
...
@@ -3308,7 +3455,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE
def
test_rescore_entrance_exam_single_student
(
self
,
act
):
""" Test re-scoring of entrance exam for single student. """
url
=
reverse
(
'rescore_entrance_exam'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
student
.
email
,
})
self
.
assertEqual
(
response
.
status_code
,
200
)
...
...
@@ -3317,7 +3464,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE
def
test_rescore_entrance_exam_all_student
(
self
):
""" Test rescoring for all students. """
url
=
reverse
(
'rescore_entrance_exam'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'all_students'
:
True
,
})
self
.
assertEqual
(
response
.
status_code
,
200
)
...
...
@@ -3325,7 +3472,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE
def
test_rescore_entrance_exam_all_student_and_single
(
self
):
""" Test re-scoring with both all students and single student parameters. """
url
=
reverse
(
'rescore_entrance_exam'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
student
.
email
,
'all_students'
:
True
,
})
...
...
@@ -3334,7 +3481,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE
def
test_rescore_entrance_exam_with_invalid_exam
(
self
):
""" Test re-scoring of entrance exam with invalid exam. """
url
=
reverse
(
'rescore_entrance_exam'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course_with_invalid_ee
.
id
)})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
student
.
email
,
})
self
.
assertEqual
(
response
.
status_code
,
400
)
...
...
@@ -3343,13 +3490,13 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE
""" Test list task history for entrance exam AND student. """
# create a re-score entrance exam task
url
=
reverse
(
'rescore_entrance_exam'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
student
.
email
,
})
self
.
assertEqual
(
response
.
status_code
,
200
)
url
=
reverse
(
'list_entrance_exam_instructor_tasks'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
student
.
email
,
})
self
.
assertEqual
(
response
.
status_code
,
200
)
...
...
@@ -3362,7 +3509,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE
def
test_list_entrance_exam_instructor_tasks_all_student
(
self
):
""" Test list task history for entrance exam AND all student. """
url
=
reverse
(
'list_entrance_exam_instructor_tasks'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
self
.
assertEqual
(
response
.
status_code
,
200
)
# check response
...
...
@@ -3373,7 +3520,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE
""" Test list task history for entrance exam failure if course has invalid exam. """
url
=
reverse
(
'list_entrance_exam_instructor_tasks'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course_with_invalid_ee
.
id
)})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'unique_student_identifier'
:
self
.
student
.
email
,
})
self
.
assertEqual
(
response
.
status_code
,
400
)
...
...
@@ -3589,7 +3736,7 @@ class TestInstructorAPITaskLists(SharedModuleStoreTestCase, LoginEnrollmentTestC
mock_factory
=
MockCompletionInfo
()
with
patch
(
'instructor.views.instructor_task_helpers.get_task_completion_info'
)
as
mock_completion_info
:
mock_completion_info
.
side_effect
=
mock_factory
.
mock_get_task_completion_info
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
self
.
assertEqual
(
response
.
status_code
,
200
)
# check response
...
...
@@ -3608,7 +3755,7 @@ class TestInstructorAPITaskLists(SharedModuleStoreTestCase, LoginEnrollmentTestC
mock_factory
=
MockCompletionInfo
()
with
patch
(
'instructor.views.instructor_task_helpers.get_task_completion_info'
)
as
mock_completion_info
:
mock_completion_info
.
side_effect
=
mock_factory
.
mock_get_task_completion_info
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
self
.
assertEqual
(
response
.
status_code
,
200
)
# check response
...
...
@@ -3627,7 +3774,7 @@ class TestInstructorAPITaskLists(SharedModuleStoreTestCase, LoginEnrollmentTestC
mock_factory
=
MockCompletionInfo
()
with
patch
(
'instructor.views.instructor_task_helpers.get_task_completion_info'
)
as
mock_completion_info
:
mock_completion_info
.
side_effect
=
mock_factory
.
mock_get_task_completion_info
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'problem_location_str'
:
self
.
problem_urlname
,
})
self
.
assertEqual
(
response
.
status_code
,
200
)
...
...
@@ -3648,7 +3795,7 @@ class TestInstructorAPITaskLists(SharedModuleStoreTestCase, LoginEnrollmentTestC
mock_factory
=
MockCompletionInfo
()
with
patch
(
'instructor.views.instructor_task_helpers.get_task_completion_info'
)
as
mock_completion_info
:
mock_completion_info
.
side_effect
=
mock_factory
.
mock_get_task_completion_info
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'problem_location_str'
:
self
.
problem_urlname
,
'unique_student_identifier'
:
self
.
student
.
email
,
})
...
...
@@ -3709,7 +3856,7 @@ class TestInstructorEmailContentList(SharedModuleStoreTestCase, LoginEnrollmentT
url
=
reverse
(
'list_email_content'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
with
patch
(
'instructor.views.api.CourseEmail.objects.get'
)
as
mock_email_info
:
mock_email_info
.
side_effect
=
self
.
get_matching_mock_email
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
self
.
assertEqual
(
response
.
status_code
,
200
)
return
response
...
...
@@ -3760,7 +3907,7 @@ class TestInstructorEmailContentList(SharedModuleStoreTestCase, LoginEnrollmentT
invalid_task
.
make_invalid_input
()
task_history_request
.
return_value
=
[
invalid_task
]
url
=
reverse
(
'list_email_content'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertTrue
(
task_history_request
.
called
)
...
...
@@ -3786,7 +3933,7 @@ class TestInstructorEmailContentList(SharedModuleStoreTestCase, LoginEnrollmentT
url
=
reverse
(
'list_email_content'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
with
patch
(
'instructor.views.api.CourseEmail.objects.get'
)
as
mock_email_info
:
mock_email_info
.
return_value
=
email
response
=
self
.
client
.
ge
t
(
url
,
{})
response
=
self
.
client
.
pos
t
(
url
,
{})
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertTrue
(
task_history_request
.
called
)
...
...
@@ -3937,7 +4084,7 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
def
test_change_due_date
(
self
):
url
=
reverse
(
'change_due_date'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'student'
:
self
.
user1
.
username
,
'url'
:
self
.
week1
.
location
.
to_deprecated_string
(),
'due_datetime'
:
'12/30/2013 00:00'
...
...
@@ -3948,7 +4095,7 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
def
test_change_to_invalid_due_date
(
self
):
url
=
reverse
(
'change_due_date'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'student'
:
self
.
user1
.
username
,
'url'
:
self
.
week1
.
location
.
to_deprecated_string
(),
'due_datetime'
:
'01/01/2009 00:00'
...
...
@@ -3961,7 +4108,7 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
def
test_change_nonexistent_due_date
(
self
):
url
=
reverse
(
'change_due_date'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'student'
:
self
.
user1
.
username
,
'url'
:
self
.
week3
.
location
.
to_deprecated_string
(),
'due_datetime'
:
'12/30/2013 00:00'
...
...
@@ -3975,7 +4122,7 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
def
test_reset_date
(
self
):
self
.
test_change_due_date
()
url
=
reverse
(
'reset_due_date'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'student'
:
self
.
user1
.
username
,
'url'
:
self
.
week1
.
location
.
to_deprecated_string
(),
})
...
...
@@ -3987,7 +4134,7 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
def
test_reset_nonexistent_extension
(
self
):
url
=
reverse
(
'reset_due_date'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'student'
:
self
.
user1
.
username
,
'url'
:
self
.
week1
.
location
.
to_deprecated_string
(),
})
...
...
@@ -3997,7 +4144,7 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
self
.
test_change_due_date
()
url
=
reverse
(
'show_unit_extensions'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
'url'
:
self
.
week1
.
location
.
to_deprecated_string
()})
response
=
self
.
client
.
pos
t
(
url
,
{
'url'
:
self
.
week1
.
location
.
to_deprecated_string
()})
self
.
assertEqual
(
response
.
status_code
,
200
,
response
.
content
)
self
.
assertEqual
(
json
.
loads
(
response
.
content
),
{
u'data'
:
[{
u'Extended Due Date'
:
u'2013-12-30 00:00'
,
...
...
@@ -4011,7 +4158,7 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
self
.
test_change_due_date
()
url
=
reverse
(
'show_student_extensions'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
'student'
:
self
.
user1
.
username
})
response
=
self
.
client
.
pos
t
(
url
,
{
'student'
:
self
.
user1
.
username
})
self
.
assertEqual
(
response
.
status_code
,
200
,
response
.
content
)
self
.
assertEqual
(
json
.
loads
(
response
.
content
),
{
u'data'
:
[{
u'Extended Due Date'
:
u'2013-12-30 00:00'
,
...
...
@@ -4105,7 +4252,7 @@ class TestDueDateExtensionsDeletedDate(ModuleStoreTestCase, LoginEnrollmentTestC
"""
url
=
reverse
(
'change_due_date'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'student'
:
self
.
user1
.
username
,
'url'
:
self
.
week1
.
location
.
to_deprecated_string
(),
'due_datetime'
:
'12/30/2013 00:00'
...
...
@@ -4118,7 +4265,7 @@ class TestDueDateExtensionsDeletedDate(ModuleStoreTestCase, LoginEnrollmentTestC
self
.
week1
=
self
.
store
.
update_item
(
self
.
week1
,
self
.
user1
.
id
)
# Now, week1's normal due date is deleted but the extension still exists.
url
=
reverse
(
'reset_due_date'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
ge
t
(
url
,
{
response
=
self
.
client
.
pos
t
(
url
,
{
'student'
:
self
.
user1
.
username
,
'url'
:
self
.
week1
.
location
.
to_deprecated_string
(),
})
...
...
@@ -4166,14 +4313,14 @@ class TestCourseIssuedCertificatesData(SharedModuleStoreTestCase):
for
__
in
xrange
(
certificate_count
):
self
.
generate_certificate
(
course_id
=
self
.
course
.
id
,
mode
=
'honor'
,
status
=
CertificateStatuses
.
generating
)
response
=
self
.
client
.
ge
t
(
url
)
response
=
self
.
client
.
pos
t
(
url
)
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertIn
(
'certificates'
,
res_json
)
self
.
assertEqual
(
len
(
res_json
[
'certificates'
]),
0
)
# Certificates with status 'downloadable' should be in response.
self
.
generate_certificate
(
course_id
=
self
.
course
.
id
,
mode
=
'honor'
,
status
=
CertificateStatuses
.
downloadable
)
response
=
self
.
client
.
ge
t
(
url
)
response
=
self
.
client
.
pos
t
(
url
)
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertIn
(
'certificates'
,
res_json
)
self
.
assertEqual
(
len
(
res_json
[
'certificates'
]),
1
)
...
...
@@ -4188,7 +4335,7 @@ class TestCourseIssuedCertificatesData(SharedModuleStoreTestCase):
for
__
in
xrange
(
certificate_count
):
self
.
generate_certificate
(
course_id
=
self
.
course
.
id
,
mode
=
'honor'
,
status
=
CertificateStatuses
.
downloadable
)
response
=
self
.
client
.
ge
t
(
url
)
response
=
self
.
client
.
pos
t
(
url
)
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertIn
(
'certificates'
,
res_json
)
self
.
assertEqual
(
len
(
res_json
[
'certificates'
]),
1
)
...
...
@@ -4207,7 +4354,7 @@ class TestCourseIssuedCertificatesData(SharedModuleStoreTestCase):
status
=
CertificateStatuses
.
downloadable
)
response
=
self
.
client
.
ge
t
(
url
)
response
=
self
.
client
.
pos
t
(
url
)
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertIn
(
'certificates'
,
res_json
)
...
...
@@ -4224,14 +4371,13 @@ class TestCourseIssuedCertificatesData(SharedModuleStoreTestCase):
Test for certificate csv features.
"""
url
=
reverse
(
'get_issued_certificates'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
url
+=
'?csv=true'
# firstly generating downloadable certificates with 'honor' mode
certificate_count
=
3
for
__
in
xrange
(
certificate_count
):
self
.
generate_certificate
(
course_id
=
self
.
course
.
id
,
mode
=
'honor'
,
status
=
CertificateStatuses
.
downloadable
)
current_date
=
datetime
.
date
.
today
()
.
strftime
(
"
%
B
%
d,
%
Y"
)
response
=
self
.
client
.
get
(
url
)
response
=
self
.
client
.
get
(
url
,
{
'csv'
:
'true'
}
)
self
.
assertEqual
(
response
[
'Content-Type'
],
'text/csv'
)
self
.
assertEqual
(
response
[
'Content-Disposition'
],
'attachment; filename={0}'
.
format
(
'issued_certificates.csv'
))
self
.
assertEqual
(
...
...
@@ -4680,7 +4826,7 @@ class TestCourseRegistrationCodes(SharedModuleStoreTestCase):
)
coupon
.
save
()
response
=
self
.
client
.
ge
t
(
get_coupon_code_url
)
response
=
self
.
client
.
pos
t
(
get_coupon_code_url
)
self
.
assertEqual
(
response
.
status_code
,
200
,
response
.
content
)
# filter all the coupons
for
coupon
in
Coupon
.
objects
.
all
():
...
...
@@ -4721,7 +4867,7 @@ class TestBulkCohorting(SharedModuleStoreTestCase):
self
.
tempdir
=
tempfile
.
mkdtemp
()
self
.
addCleanup
(
shutil
.
rmtree
,
self
.
tempdir
)
def
call_add_users_to_cohorts
(
self
,
csv_data
,
suffix
=
'.csv'
,
method
=
'POST'
):
def
call_add_users_to_cohorts
(
self
,
csv_data
,
suffix
=
'.csv'
):
"""
Call `add_users_to_cohorts` with a file generated from `csv_data`.
"""
...
...
@@ -4731,10 +4877,7 @@ class TestBulkCohorting(SharedModuleStoreTestCase):
file_pointer
.
write
(
csv_data
.
encode
(
'utf-8'
))
with
open
(
file_name
,
'r'
)
as
file_pointer
:
url
=
reverse
(
'add_users_to_cohorts'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)})
if
method
==
'POST'
:
return
self
.
client
.
post
(
url
,
{
'uploaded-file'
:
file_pointer
})
elif
method
==
'GET'
:
return
self
.
client
.
get
(
url
,
{
'uploaded-file'
:
file_pointer
})
return
self
.
client
.
post
(
url
,
{
'uploaded-file'
:
file_pointer
})
def
expect_error_on_file_content
(
self
,
file_content
,
error
,
file_suffix
=
'.csv'
):
"""
...
...
@@ -4803,14 +4946,6 @@ class TestBulkCohorting(SharedModuleStoreTestCase):
response
=
self
.
call_add_users_to_cohorts
(
''
)
self
.
assertEqual
(
response
.
status_code
,
403
)
def
test_post_only
(
self
):
"""
Verify that we can't call the view when we aren't using POST.
"""
self
.
client
.
login
(
username
=
self
.
staff_user
.
username
,
password
=
'test'
)
response
=
self
.
call_add_users_to_cohorts
(
''
,
method
=
'GET'
)
self
.
assertEqual
(
response
.
status_code
,
405
)
@patch
(
'instructor.views.api.instructor_task.api.submit_cohort_students'
)
@patch
(
'instructor.views.api.store_uploaded_file'
)
def
test_success_username
(
self
,
mock_store_upload
,
mock_cohort_task
):
...
...
This diff is collapsed.
Click to expand it.
lms/djangoapps/instructor/views/api.py
View file @
d54f79f5
...
...
@@ -140,51 +140,14 @@ def common_exceptions_400(func):
return
wrapped
def
require_query_params
(
*
args
,
**
kwargs
):
"""
Checks for required paremters or renders a 400 error.
(decorator with arguments)
`args` is a *list of required GET parameter names.
`kwargs` is a **dict of required GET parameter names
to string explanations of the parameter
"""
required_params
=
[]
required_params
+=
[(
arg
,
None
)
for
arg
in
args
]
required_params
+=
[(
key
,
kwargs
[
key
])
for
key
in
kwargs
]
# required_params = e.g. [('action', 'enroll or unenroll'), ['emails', None]]
def
decorator
(
func
):
# pylint: disable=missing-docstring
def
wrapped
(
*
args
,
**
kwargs
):
# pylint: disable=missing-docstring
request
=
args
[
0
]
error_response_data
=
{
'error'
:
'Missing required query parameter(s)'
,
'parameters'
:
[],
'info'
:
{},
}
for
(
param
,
extra
)
in
required_params
:
default
=
object
()
if
request
.
GET
.
get
(
param
,
default
)
==
default
:
error_response_data
[
'parameters'
]
.
append
(
param
)
error_response_data
[
'info'
][
param
]
=
extra
if
len
(
error_response_data
[
'parameters'
])
>
0
:
return
JsonResponse
(
error_response_data
,
status
=
400
)
else
:
return
func
(
*
args
,
**
kwargs
)
return
wrapped
return
decorator
def
require_post_params
(
*
args
,
**
kwargs
):
"""
Checks for required parameters or renders a 400 error.
(decorator with arguments)
Functions like 'require_query_params', but checks for
POST parameters rather than GET parameters.
`args` is a *list of required POST parameter names.
`kwargs` is a **dict of required POST parameter names
to string explanations of the parameter
"""
required_params
=
[]
required_params
+=
[(
arg
,
None
)
for
arg
in
args
]
...
...
@@ -314,6 +277,7 @@ NAME_INDEX = 2
COUNTRY_INDEX
=
3
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -604,6 +568,7 @@ def create_and_enroll_user(email, username, name, country, password, course_id,
return
errors
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -768,6 +733,7 @@ def students_update_enrollment(request, course_id):
return
JsonResponse
(
response_payload
)
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'instructor'
)
...
...
@@ -848,11 +814,12 @@ def bulk_beta_modify_access(request, course_id):
return
JsonResponse
(
response_payload
)
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'instructor'
)
@common_exceptions_400
@require_
query
_params
(
@require_
post
_params
(
unique_student_identifier
=
"email or username of user to change access"
,
rolename
=
"'instructor', 'staff', 'beta', or 'ccx_coach'"
,
action
=
"'allow' or 'revoke'"
...
...
@@ -874,10 +841,10 @@ def modify_access(request, course_id):
request
.
user
,
'instructor'
,
course_id
,
depth
=
None
)
try
:
user
=
get_student_from_identifier
(
request
.
GE
T
.
get
(
'unique_student_identifier'
))
user
=
get_student_from_identifier
(
request
.
POS
T
.
get
(
'unique_student_identifier'
))
except
User
.
DoesNotExist
:
response_payload
=
{
'unique_student_identifier'
:
request
.
GE
T
.
get
(
'unique_student_identifier'
),
'unique_student_identifier'
:
request
.
POS
T
.
get
(
'unique_student_identifier'
),
'userDoesNotExist'
:
True
,
}
return
JsonResponse
(
response_payload
)
...
...
@@ -892,8 +859,8 @@ def modify_access(request, course_id):
}
return
JsonResponse
(
response_payload
)
rolename
=
request
.
GE
T
.
get
(
'rolename'
)
action
=
request
.
GE
T
.
get
(
'action'
)
rolename
=
request
.
POS
T
.
get
(
'rolename'
)
action
=
request
.
POS
T
.
get
(
'action'
)
if
rolename
not
in
ROLES
:
error
=
strip_tags
(
"unknown rolename '{}'"
.
format
(
rolename
))
...
...
@@ -928,10 +895,11 @@ def modify_access(request, course_id):
return
JsonResponse
(
response_payload
)
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'instructor'
)
@require_
query
_params
(
rolename
=
"'instructor', 'staff', or 'beta'"
)
@require_
post
_params
(
rolename
=
"'instructor', 'staff', or 'beta'"
)
def
list_course_role_members
(
request
,
course_id
):
"""
List instructors and staff.
...
...
@@ -956,7 +924,7 @@ def list_course_role_members(request, course_id):
request
.
user
,
'instructor'
,
course_id
,
depth
=
None
)
rolename
=
request
.
GE
T
.
get
(
'rolename'
)
rolename
=
request
.
POS
T
.
get
(
'rolename'
)
if
rolename
not
in
ROLES
:
return
HttpResponseBadRequest
()
...
...
@@ -980,6 +948,7 @@ def list_course_role_members(request, course_id):
@transaction.non_atomic_requests
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -996,7 +965,7 @@ def get_problem_responses(request, course_id):
Responds with BadRequest if problem location is faulty.
"""
course_key
=
CourseKey
.
from_string
(
course_id
)
problem_location
=
request
.
GE
T
.
get
(
'problem_location'
,
''
)
problem_location
=
request
.
POS
T
.
get
(
'problem_location'
,
''
)
try
:
problem_key
=
UsageKey
.
from_string
(
problem_location
)
...
...
@@ -1025,6 +994,7 @@ def get_problem_responses(request, course_id):
return
JsonResponse
({
"status"
:
already_running_status
})
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -1223,6 +1193,7 @@ def get_issued_certificates(request, course_id):
@transaction.non_atomic_requests
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -1315,6 +1286,7 @@ def get_students_features(request, course_id, csv=False): # pylint: disable=red
@transaction.non_atomic_requests
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -1422,6 +1394,7 @@ def get_coupon_codes(request, course_id): # pylint: disable=unused-argument
@transaction.non_atomic_requests
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -1446,6 +1419,7 @@ def get_enrollment_report(request, course_id):
@transaction.non_atomic_requests
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -1471,6 +1445,7 @@ def get_exec_summary_report(request, course_id):
@transaction.non_atomic_requests
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -1495,6 +1470,7 @@ def get_course_survey_results(request, course_id):
@transaction.non_atomic_requests
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -1901,11 +1877,12 @@ def get_anon_ids(request, course_id): # pylint: disable=unused-argument
return
csv_response
(
course_id
.
to_deprecated_string
()
.
replace
(
'/'
,
'-'
)
+
'-anon-ids.csv'
,
header
,
rows
)
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@common_exceptions_400
@require_level
(
'staff'
)
@require_
query
_params
(
@require_
post
_params
(
unique_student_identifier
=
"email or username of student for whom to get progress url"
)
def
get_student_progress_url
(
request
,
course_id
):
...
...
@@ -1913,13 +1890,13 @@ def get_student_progress_url(request, course_id):
Get the progress url of a student.
Limited to staff access.
Takes query par
e
meter unique_student_identifier and if the student exists
Takes query par
a
meter unique_student_identifier and if the student exists
returns e.g. {
'progress_url': '/../...'
}
"""
course_id
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
user
=
get_student_from_identifier
(
request
.
GE
T
.
get
(
'unique_student_identifier'
))
user
=
get_student_from_identifier
(
request
.
POS
T
.
get
(
'unique_student_identifier'
))
progress_url
=
reverse
(
'student_progress'
,
kwargs
=
{
'course_id'
:
course_id
.
to_deprecated_string
(),
'student_id'
:
user
.
id
})
...
...
@@ -1931,10 +1908,11 @@ def get_student_progress_url(request, course_id):
@transaction.non_atomic_requests
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
@require_
query
_params
(
@require_
post
_params
(
problem_to_reset
=
"problem urlname to reset"
)
@common_exceptions_400
...
...
@@ -1961,13 +1939,13 @@ def reset_student_attempts(request, course_id):
request
.
user
,
'staff'
,
course_id
,
depth
=
None
)
problem_to_reset
=
strip_if_string
(
request
.
GE
T
.
get
(
'problem_to_reset'
))
student_identifier
=
request
.
GE
T
.
get
(
'unique_student_identifier'
,
None
)
problem_to_reset
=
strip_if_string
(
request
.
POS
T
.
get
(
'problem_to_reset'
))
student_identifier
=
request
.
POS
T
.
get
(
'unique_student_identifier'
,
None
)
student
=
None
if
student_identifier
is
not
None
:
student
=
get_student_from_identifier
(
student_identifier
)
all_students
=
request
.
GE
T
.
get
(
'all_students'
,
False
)
in
[
'true'
,
'True'
,
True
]
delete_module
=
request
.
GE
T
.
get
(
'delete_module'
,
False
)
in
[
'true'
,
'True'
,
True
]
all_students
=
request
.
POS
T
.
get
(
'all_students'
,
False
)
in
[
'true'
,
'True'
,
True
]
delete_module
=
request
.
POS
T
.
get
(
'delete_module'
,
False
)
in
[
'true'
,
'True'
,
True
]
# parameter combinations
if
all_students
and
student
:
...
...
@@ -2019,6 +1997,7 @@ def reset_student_attempts(request, course_id):
@transaction.non_atomic_requests
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -2049,12 +2028,12 @@ def reset_student_attempts_for_entrance_exam(request, course_id): # pylint: dis
_
(
"Course has no entrance exam section."
)
)
student_identifier
=
request
.
GE
T
.
get
(
'unique_student_identifier'
,
None
)
student_identifier
=
request
.
POS
T
.
get
(
'unique_student_identifier'
,
None
)
student
=
None
if
student_identifier
is
not
None
:
student
=
get_student_from_identifier
(
student_identifier
)
all_students
=
request
.
GE
T
.
get
(
'all_students'
,
False
)
in
[
'true'
,
'True'
,
True
]
delete_module
=
request
.
GE
T
.
get
(
'delete_module'
,
False
)
in
[
'true'
,
'True'
,
True
]
all_students
=
request
.
POS
T
.
get
(
'all_students'
,
False
)
in
[
'true'
,
'True'
,
True
]
delete_module
=
request
.
POS
T
.
get
(
'delete_module'
,
False
)
in
[
'true'
,
'True'
,
True
]
# parameter combinations
if
all_students
and
student
:
...
...
@@ -2085,10 +2064,11 @@ def reset_student_attempts_for_entrance_exam(request, course_id): # pylint: dis
@transaction.non_atomic_requests
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'instructor'
)
@require_
query
_params
(
problem_to_reset
=
"problem urlname to reset"
)
@require_
post
_params
(
problem_to_reset
=
"problem urlname to reset"
)
@common_exceptions_400
def
rescore_problem
(
request
,
course_id
):
"""
...
...
@@ -2103,13 +2083,13 @@ def rescore_problem(request, course_id):
all_students and unique_student_identifier cannot both be present.
"""
course_id
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
problem_to_reset
=
strip_if_string
(
request
.
GE
T
.
get
(
'problem_to_reset'
))
student_identifier
=
request
.
GE
T
.
get
(
'unique_student_identifier'
,
None
)
problem_to_reset
=
strip_if_string
(
request
.
POS
T
.
get
(
'problem_to_reset'
))
student_identifier
=
request
.
POS
T
.
get
(
'unique_student_identifier'
,
None
)
student
=
None
if
student_identifier
is
not
None
:
student
=
get_student_from_identifier
(
student_identifier
)
all_students
=
request
.
GE
T
.
get
(
'all_students'
)
in
[
'true'
,
'True'
,
True
]
all_students
=
request
.
POS
T
.
get
(
'all_students'
)
in
[
'true'
,
'True'
,
True
]
if
not
(
problem_to_reset
and
(
all_students
or
student
)):
return
HttpResponseBadRequest
(
"Missing query parameters."
)
...
...
@@ -2141,6 +2121,7 @@ def rescore_problem(request, course_id):
@transaction.non_atomic_requests
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'instructor'
)
...
...
@@ -2161,12 +2142,12 @@ def rescore_entrance_exam(request, course_id):
request
.
user
,
'staff'
,
course_id
,
depth
=
None
)
student_identifier
=
request
.
GE
T
.
get
(
'unique_student_identifier'
,
None
)
student_identifier
=
request
.
POS
T
.
get
(
'unique_student_identifier'
,
None
)
student
=
None
if
student_identifier
is
not
None
:
student
=
get_student_from_identifier
(
student_identifier
)
all_students
=
request
.
GE
T
.
get
(
'all_students'
)
in
[
'true'
,
'True'
,
True
]
all_students
=
request
.
POS
T
.
get
(
'all_students'
)
in
[
'true'
,
'True'
,
True
]
if
not
course
.
entrance_exam_id
:
return
HttpResponseBadRequest
(
...
...
@@ -2193,6 +2174,7 @@ def rescore_entrance_exam(request, course_id):
return
JsonResponse
(
response_payload
)
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -2211,6 +2193,7 @@ def list_background_email_tasks(request, course_id): # pylint: disable=unused-a
return
JsonResponse
(
response_payload
)
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -2229,6 +2212,7 @@ def list_email_content(request, course_id): # pylint: disable=unused-argument
return
JsonResponse
(
response_payload
)
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -2243,8 +2227,8 @@ def list_instructor_tasks(request, course_id):
history for problem AND student (intersection)
"""
course_id
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
problem_location_str
=
strip_if_string
(
request
.
GE
T
.
get
(
'problem_location_str'
,
False
))
student
=
request
.
GE
T
.
get
(
'unique_student_identifier'
,
None
)
problem_location_str
=
strip_if_string
(
request
.
POS
T
.
get
(
'problem_location_str'
,
False
))
student
=
request
.
POS
T
.
get
(
'unique_student_identifier'
,
None
)
if
student
is
not
None
:
student
=
get_student_from_identifier
(
student
)
...
...
@@ -2274,6 +2258,7 @@ def list_instructor_tasks(request, course_id):
return
JsonResponse
(
response_payload
)
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -2287,7 +2272,7 @@ def list_entrance_exam_instructor_tasks(request, course_id): # pylint: disable=
"""
course_id
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
course
=
get_course_by_id
(
course_id
)
student
=
request
.
GE
T
.
get
(
'unique_student_identifier'
,
None
)
student
=
request
.
POS
T
.
get
(
'unique_student_identifier'
,
None
)
if
student
is
not
None
:
student
=
get_student_from_identifier
(
student
)
...
...
@@ -2308,6 +2293,7 @@ def list_entrance_exam_instructor_tasks(request, course_id): # pylint: disable=
return
JsonResponse
(
response_payload
)
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -2327,6 +2313,7 @@ def list_report_downloads(_request, course_id):
return
JsonResponse
(
response_payload
)
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -2348,6 +2335,7 @@ def list_financial_report_downloads(_request, course_id):
@transaction.non_atomic_requests
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -2373,6 +2361,7 @@ def export_ora2_data(request, course_id):
@transaction.non_atomic_requests
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -2394,6 +2383,7 @@ def calculate_grades_csv(request, course_id):
@transaction.non_atomic_requests
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -2420,10 +2410,11 @@ def problem_grade_report(request, course_id):
})
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
@require_
query
_params
(
'rolename'
)
@require_
post
_params
(
'rolename'
)
def
list_forum_members
(
request
,
course_id
):
"""
Lists forum members of a certain rolename.
...
...
@@ -2442,7 +2433,7 @@ def list_forum_members(request, course_id):
request
.
user
,
course_id
,
FORUM_ROLE_ADMINISTRATOR
)
rolename
=
request
.
GE
T
.
get
(
'rolename'
)
rolename
=
request
.
POS
T
.
get
(
'rolename'
)
# default roles require either (staff & forum admin) or (instructor)
if
not
(
has_forum_admin
or
has_instructor_access
):
...
...
@@ -2483,6 +2474,7 @@ def list_forum_members(request, course_id):
@transaction.non_atomic_requests
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
...
...
@@ -2539,10 +2531,11 @@ def send_email(request, course_id):
return
JsonResponse
(
response_payload
)
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
@require_
query
_params
(
@require_
post
_params
(
unique_student_identifier
=
"email or username of user to change access"
,
rolename
=
"the forum role"
,
action
=
"'allow' or 'revoke'"
,
...
...
@@ -2569,9 +2562,9 @@ def update_forum_role_membership(request, course_id):
request
.
user
,
course_id
,
FORUM_ROLE_ADMINISTRATOR
)
unique_student_identifier
=
request
.
GE
T
.
get
(
'unique_student_identifier'
)
rolename
=
request
.
GE
T
.
get
(
'rolename'
)
action
=
request
.
GE
T
.
get
(
'action'
)
unique_student_identifier
=
request
.
POS
T
.
get
(
'unique_student_identifier'
)
rolename
=
request
.
POS
T
.
get
(
'rolename'
)
action
=
request
.
POS
T
.
get
(
'action'
)
# default roles require either (staff & forum admin) or (instructor)
if
not
(
has_forum_admin
or
has_instructor_access
):
...
...
@@ -2629,18 +2622,19 @@ def _display_unit(unit):
@handle_dashboard_error
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
@require_
query
_params
(
'student'
,
'url'
,
'due_datetime'
)
@require_
post
_params
(
'student'
,
'url'
,
'due_datetime'
)
def
change_due_date
(
request
,
course_id
):
"""
Grants a due date extension to a student for a particular unit.
"""
course
=
get_course_by_id
(
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
))
student
=
require_student_from_identifier
(
request
.
GE
T
.
get
(
'student'
))
unit
=
find_unit
(
course
,
request
.
GE
T
.
get
(
'url'
))
due_date
=
parse_datetime
(
request
.
GE
T
.
get
(
'due_datetime'
))
student
=
require_student_from_identifier
(
request
.
POS
T
.
get
(
'student'
))
unit
=
find_unit
(
course
,
request
.
POS
T
.
get
(
'url'
))
due_date
=
parse_datetime
(
request
.
POS
T
.
get
(
'due_datetime'
))
set_due_date_extension
(
course
,
unit
,
student
,
due_date
)
return
JsonResponse
(
_
(
...
...
@@ -2650,17 +2644,18 @@ def change_due_date(request, course_id):
@handle_dashboard_error
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
@require_
query
_params
(
'student'
,
'url'
)
@require_
post
_params
(
'student'
,
'url'
)
def
reset_due_date
(
request
,
course_id
):
"""
Rescinds a due date extension for a student on a particular unit.
"""
course
=
get_course_by_id
(
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
))
student
=
require_student_from_identifier
(
request
.
GE
T
.
get
(
'student'
))
unit
=
find_unit
(
course
,
request
.
GE
T
.
get
(
'url'
))
student
=
require_student_from_identifier
(
request
.
POS
T
.
get
(
'student'
))
unit
=
find_unit
(
course
,
request
.
POS
T
.
get
(
'url'
))
set_due_date_extension
(
course
,
unit
,
student
,
None
)
if
not
getattr
(
unit
,
"due"
,
None
):
# It's possible the normal due date was deleted after an extension was granted:
...
...
@@ -2676,30 +2671,32 @@ def reset_due_date(request, course_id):
@handle_dashboard_error
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
@require_
query
_params
(
'url'
)
@require_
post
_params
(
'url'
)
def
show_unit_extensions
(
request
,
course_id
):
"""
Shows all of the students which have due date extensions for the given unit.
"""
course
=
get_course_by_id
(
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
))
unit
=
find_unit
(
course
,
request
.
GE
T
.
get
(
'url'
))
unit
=
find_unit
(
course
,
request
.
POS
T
.
get
(
'url'
))
return
JsonResponse
(
dump_module_extensions
(
course
,
unit
))
@handle_dashboard_error
@require_POST
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
@require_
query
_params
(
'student'
)
@require_
post
_params
(
'student'
)
def
show_student_extensions
(
request
,
course_id
):
"""
Shows all of the due date extensions granted to a particular student in a
particular course.
"""
student
=
require_student_from_identifier
(
request
.
GE
T
.
get
(
'student'
))
student
=
require_student_from_identifier
(
request
.
POS
T
.
get
(
'student'
))
course
=
get_course_by_id
(
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
))
return
JsonResponse
(
dump_student_extensions
(
course
,
student
))
...
...
@@ -3076,7 +3073,6 @@ def generate_certificate_exceptions(request, course_id, generate_for=None):
return
JsonResponse
(
response_payload
)
@csrf_exempt
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_global_staff
@require_POST
...
...
This diff is collapsed.
Click to expand it.
lms/static/coffee/src/instructor_dashboard/data_download.coffee
View file @
d54f79f5
...
...
@@ -110,6 +110,7 @@ class DataDownload
url
=
@
$list_proctored_exam_results_csv_btn
.
data
'endpoint'
# display html from proctored exam results config endpoint
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
url
error
:
(
std_ajax_err
)
=>
...
...
@@ -129,6 +130,7 @@ class DataDownload
url
=
@
$survey_results_csv_btn
.
data
'endpoint'
# display html from survey results config endpoint
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
url
error
:
(
std_ajax_err
)
=>
...
...
@@ -153,6 +155,7 @@ class DataDownload
url
+=
'/csv'
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
url
error
:
(
std_ajax_err
)
=>
...
...
@@ -171,6 +174,7 @@ class DataDownload
# fetch user list
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
url
error
:
(
std_ajax_err
)
=>
...
...
@@ -199,6 +203,7 @@ class DataDownload
url
=
@
$list_problem_responses_csv_btn
.
data
'endpoint'
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
url
data
:
...
...
@@ -215,6 +220,7 @@ class DataDownload
url
=
@
$list_may_enroll_csv_btn
.
data
'endpoint'
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
url
error
:
(
std_ajax_err
)
=>
...
...
@@ -228,6 +234,7 @@ class DataDownload
url
=
@
$grade_config_btn
.
data
'endpoint'
# display html from grading config endpoint
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
url
error
:
(
std_ajax_err
)
=>
...
...
@@ -244,6 +251,7 @@ class DataDownload
@
clear_display
()
url
=
$
(
e
.
target
).
data
'endpoint'
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
url
error
:
std_ajax_err
=>
...
...
This diff is collapsed.
Click to expand it.
lms/static/coffee/src/instructor_dashboard/extensions.coffee
View file @
d54f79f5
...
...
@@ -45,6 +45,7 @@ class Extensions
due_datetime
:
@
$due_datetime_input
.
val
()
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
@
$change_due_date
.
data
'endpoint'
data
:
send_data
...
...
@@ -60,6 +61,7 @@ class Extensions
url
:
@
$url_input
.
val
()
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
@
$reset_due_date
.
data
'endpoint'
data
:
send_data
...
...
@@ -75,6 +77,7 @@ class Extensions
send_data
=
url
:
@
$url_input
.
val
()
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
url
data
:
send_data
...
...
@@ -90,6 +93,7 @@ class Extensions
send_data
=
student
:
@
$student_input
.
val
()
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
url
data
:
send_data
...
...
This diff is collapsed.
Click to expand it.
lms/static/coffee/src/instructor_dashboard/membership.coffee
View file @
d54f79f5
...
...
@@ -137,6 +137,7 @@ class AuthListWidget extends MemberListWidget
# `cb` is called with cb(error, member_list)
get_member_list
:
(
cb
)
->
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
@
list_endpoint
data
:
rolename
:
@
rolename
...
...
@@ -151,6 +152,7 @@ class AuthListWidget extends MemberListWidget
# `cb` is called with cb(error, data)
modify_member_access
:
(
unique_student_identifier
,
action
,
cb
)
->
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
@
modify_endpoint
data
:
...
...
@@ -645,6 +647,7 @@ class AuthList
# the endpoint comes from data-endpoint of the table
$
.
ajax
dataType
:
'json'
type
:
'POST'
url
:
@
$display_table
.
data
'endpoint'
data
:
rolename
:
@
rolename
success
:
load_auth_list
...
...
@@ -664,6 +667,7 @@ class AuthList
access_change
:
(
email
,
action
,
cb
)
->
$
.
ajax
dataType
:
'json'
type
:
'POST'
url
:
@
$add_section
.
data
'endpoint'
data
:
email
:
email
...
...
This diff is collapsed.
Click to expand it.
lms/static/coffee/src/instructor_dashboard/send_email.coffee
View file @
d54f79f5
...
...
@@ -99,6 +99,7 @@ class @SendEmail
@
$btn_task_history_email
.
click
=>
url
=
@
$btn_task_history_email
.
data
'endpoint'
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
url
success
:
(
data
)
=>
...
...
@@ -115,6 +116,7 @@ class @SendEmail
@
$btn_task_history_email_content
.
click
=>
url
=
@
$btn_task_history_email_content
.
data
'endpoint'
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
url
success
:
(
data
)
=>
...
...
This diff is collapsed.
Click to expand it.
lms/static/coffee/src/instructor_dashboard/student_admin.coffee
View file @
d54f79f5
...
...
@@ -76,6 +76,7 @@ class @StudentAdmin
full_error_message
=
_
.
template
(
error_message
)({
student_id
:
unique_student_identifier
})
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
@
$progress_link
.
data
'endpoint'
data
:
unique_student_identifier
:
unique_student_identifier
...
...
@@ -101,6 +102,7 @@ class @StudentAdmin
full_error_message
=
_
.
template
(
error_message
)({
problem_id
:
problem_to_reset
,
student_id
:
unique_student_identifier
})
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
@
$btn_reset_attempts_single
.
data
'endpoint'
data
:
send_data
...
...
@@ -127,6 +129,7 @@ class @StudentAdmin
full_error_message
=
_
.
template
(
error_message
)({
student_id
:
unique_student_identifier
,
problem_id
:
problem_to_reset
})
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
@
$btn_delete_state_single
.
data
'endpoint'
data
:
send_data
...
...
@@ -153,6 +156,7 @@ class @StudentAdmin
full_error_message
=
_
.
template
(
error_message
)({
student_id
:
unique_student_identifier
,
problem_id
:
problem_to_reset
})
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
@
$btn_rescore_problem_single
.
data
'endpoint'
data
:
send_data
...
...
@@ -174,6 +178,7 @@ class @StudentAdmin
full_error_message
=
_
.
template
(
error_message
)({
student_id
:
unique_student_identifier
,
problem_id
:
problem_to_reset
})
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
@
$btn_task_history_single
.
data
'endpoint'
data
:
send_data
...
...
@@ -191,6 +196,7 @@ class @StudentAdmin
delete_module
:
false
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
@
$btn_reset_entrance_exam_attempts
.
data
'endpoint'
data
:
send_data
...
...
@@ -212,6 +218,7 @@ class @StudentAdmin
unique_student_identifier
:
unique_student_identifier
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
@
$btn_rescore_entrance_exam
.
data
'endpoint'
data
:
send_data
...
...
@@ -256,6 +263,7 @@ class @StudentAdmin
delete_module
:
true
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
@
$btn_delete_entrance_exam_state
.
data
'endpoint'
data
:
send_data
...
...
@@ -277,6 +285,7 @@ class @StudentAdmin
unique_student_identifier
:
unique_student_identifier
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
@
$btn_entrance_exam_task_history
.
data
'endpoint'
data
:
send_data
...
...
@@ -304,6 +313,7 @@ class @StudentAdmin
full_error_message
=
_
.
template
(
error_message
)({
problem_id
:
problem_to_reset
})
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
@
$btn_reset_attempts_all
.
data
'endpoint'
data
:
send_data
...
...
@@ -330,6 +340,7 @@ class @StudentAdmin
full_error_message
=
_
.
template
(
error_message
)({
problem_id
:
problem_to_reset
})
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
@
$btn_rescore_problem_all
.
data
'endpoint'
data
:
send_data
...
...
@@ -348,6 +359,7 @@ class @StudentAdmin
return
@
$request_response_error_all
.
text
gettext
(
"Please enter a problem location."
)
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
@
$btn_task_history_all
.
data
'endpoint'
data
:
send_data
...
...
This diff is collapsed.
Click to expand it.
lms/static/coffee/src/instructor_dashboard/util.coffee
View file @
d54f79f5
...
...
@@ -336,6 +336,7 @@ class @PendingInstructorTasks
reload_running_tasks_list
:
=>
list_endpoint
=
@
$table_running_tasks
.
data
'endpoint'
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
list_endpoint
success
:
(
data
)
=>
...
...
@@ -392,6 +393,7 @@ class ReportDownloads
reload_report_downloads
:
->
endpoint
=
@
$report_downloads_table
.
data
'endpoint'
$
.
ajax
type
:
'POST'
dataType
:
'json'
url
:
endpoint
success
:
(
data
)
=>
...
...
This diff is collapsed.
Click to expand it.
lms/static/js/instructor_dashboard/ecommerce.js
View file @
d54f79f5
...
...
@@ -45,6 +45,7 @@ var edx = edx || {};
$
(
'input[name="user-enrollment-report"]'
).
click
(
function
(){
var
url
=
$
(
this
).
data
(
'endpoint'
);
$
.
ajax
({
type
:
'POST'
,
dataType
:
"json"
,
url
:
url
,
success
:
function
(
data
)
{
...
...
@@ -64,6 +65,7 @@ var edx = edx || {};
$
(
'input[name="exec-summary-report"]'
).
click
(
function
(){
var
url
=
$
(
this
).
data
(
'endpoint'
);
$
.
ajax
({
type
:
'POST'
,
dataType
:
"json"
,
url
:
url
,
success
:
function
(
data
)
{
...
...
This diff is collapsed.
Click to expand it.
lms/static/js/spec/instructor_dashboard/student_admin_spec.js
View file @
d54f79f5
...
...
@@ -36,15 +36,15 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'common/js/sp
// Spy on AJAX requests
var
requests
=
AjaxHelpers
.
requests
(
this
);
studentadmin
.
$field_entrance_exam_student_select_grade
.
val
(
unique_student_identifier
)
studentadmin
.
$field_entrance_exam_student_select_grade
.
val
(
unique_student_identifier
)
;
studentadmin
.
$btn_reset_entrance_exam_attempts
.
click
();
// Verify that the client contacts the server to start instructor task
var
params
=
$
.
param
({
unique_student_identifier
:
unique_student_identifier
,
delete_module
:
false
});
var
url
=
dashboard_api_url
+
'/reset_student_attempts_for_entrance_exam
?'
+
params
;
AjaxHelpers
.
expect
JsonRequest
(
requests
,
'GET'
,
url
);
var
url
=
dashboard_api_url
+
'/reset_student_attempts_for_entrance_exam
'
;
AjaxHelpers
.
expect
PostRequest
(
requests
,
url
,
params
);
// Simulate a success response from the server
AjaxHelpers
.
respondWithJson
(
requests
,
{
...
...
@@ -63,8 +63,8 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'common/js/sp
unique_student_identifier
:
unique_student_identifier
,
delete_module
:
false
});
var
url
=
dashboard_api_url
+
'/reset_student_attempts_for_entrance_exam
?'
+
params
;
AjaxHelpers
.
expect
JsonRequest
(
requests
,
'GET'
,
url
);
var
url
=
dashboard_api_url
+
'/reset_student_attempts_for_entrance_exam
'
;
AjaxHelpers
.
expect
PostRequest
(
requests
,
url
,
params
);
// Simulate an error response from the server
AjaxHelpers
.
respondWithError
(
requests
,
400
,{});
...
...
@@ -96,8 +96,8 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'common/js/sp
var
params
=
$
.
param
({
unique_student_identifier
:
unique_student_identifier
});
var
url
=
dashboard_api_url
+
'/rescore_entrance_exam
?'
+
params
;
AjaxHelpers
.
expect
JsonRequest
(
requests
,
'GET'
,
url
);
var
url
=
dashboard_api_url
+
'/rescore_entrance_exam
'
;
AjaxHelpers
.
expect
PostRequest
(
requests
,
url
,
params
);
// Simulate a success response from the server
AjaxHelpers
.
respondWithJson
(
requests
,
{
...
...
@@ -115,8 +115,8 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'common/js/sp
var
params
=
$
.
param
({
unique_student_identifier
:
unique_student_identifier
});
var
url
=
dashboard_api_url
+
'/rescore_entrance_exam
?'
+
params
;
AjaxHelpers
.
expect
JsonRequest
(
requests
,
'GET'
,
url
);
var
url
=
dashboard_api_url
+
'/rescore_entrance_exam
'
;
AjaxHelpers
.
expect
PostRequest
(
requests
,
url
,
params
);
// Simulate an error response from the server
AjaxHelpers
.
respondWithError
(
requests
,
400
,{});
...
...
@@ -195,8 +195,8 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'common/js/sp
unique_student_identifier
:
unique_student_identifier
,
delete_module
:
true
});
var
url
=
dashboard_api_url
+
'/reset_student_attempts_for_entrance_exam
?'
+
params
;
AjaxHelpers
.
expect
JsonRequest
(
requests
,
'GET'
,
url
);
var
url
=
dashboard_api_url
+
'/reset_student_attempts_for_entrance_exam
'
;
AjaxHelpers
.
expect
PostRequest
(
requests
,
url
,
params
);
// Simulate a success response from the server
AjaxHelpers
.
respondWithJson
(
requests
,
{
...
...
@@ -215,8 +215,8 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'common/js/sp
unique_student_identifier
:
unique_student_identifier
,
delete_module
:
true
});
var
url
=
dashboard_api_url
+
'/reset_student_attempts_for_entrance_exam
?'
+
params
;
AjaxHelpers
.
expect
JsonRequest
(
requests
,
'GET'
,
url
);
var
url
=
dashboard_api_url
+
'/reset_student_attempts_for_entrance_exam
'
;
AjaxHelpers
.
expect
PostRequest
(
requests
,
url
,
params
);
// Simulate an error response from the server
AjaxHelpers
.
respondWithError
(
requests
,
400
,{});
...
...
@@ -248,8 +248,8 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'common/js/sp
var
params
=
$
.
param
({
unique_student_identifier
:
unique_student_identifier
});
var
url
=
dashboard_api_url
+
'/list_entrance_exam_instructor_tasks
?'
+
params
;
AjaxHelpers
.
expect
JsonRequest
(
requests
,
'GET'
,
url
);
var
url
=
dashboard_api_url
+
'/list_entrance_exam_instructor_tasks
'
;
AjaxHelpers
.
expect
PostRequest
(
requests
,
url
,
params
);
// Simulate a success response from the server
AjaxHelpers
.
respondWithJson
(
requests
,
{
...
...
@@ -279,8 +279,8 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'common/js/sp
var
params
=
$
.
param
({
unique_student_identifier
:
unique_student_identifier
});
var
url
=
dashboard_api_url
+
'/list_entrance_exam_instructor_tasks
?'
+
params
;
AjaxHelpers
.
expect
JsonRequest
(
requests
,
'GET'
,
url
);
var
url
=
dashboard_api_url
+
'/list_entrance_exam_instructor_tasks
'
;
AjaxHelpers
.
expect
PostRequest
(
requests
,
url
,
params
);
// Simulate an error response from the server
AjaxHelpers
.
respondWithError
(
requests
,
400
,{});
...
...
This diff is collapsed.
Click to expand it.
lms/static/js/spec/staff_debug_actions_spec.js
View file @
d54f79f5
...
...
@@ -91,7 +91,7 @@ define([
spyOn
(
$
,
'ajax'
);
StaffDebug
.
reset
(
locationName
,
location
);
expect
(
$
.
ajax
.
calls
.
mostRecent
().
args
[
0
].
type
).
toEqual
(
'
GE
T'
);
expect
(
$
.
ajax
.
calls
.
mostRecent
().
args
[
0
].
type
).
toEqual
(
'
POS
T'
);
expect
(
$
.
ajax
.
calls
.
mostRecent
().
args
[
0
].
data
).
toEqual
({
'problem_to_reset'
:
location
,
'unique_student_identifier'
:
'userman'
,
...
...
@@ -110,7 +110,7 @@ define([
spyOn
(
$
,
'ajax'
);
StaffDebug
.
sdelete
(
locationName
,
location
);
expect
(
$
.
ajax
.
calls
.
mostRecent
().
args
[
0
].
type
).
toEqual
(
'
GE
T'
);
expect
(
$
.
ajax
.
calls
.
mostRecent
().
args
[
0
].
type
).
toEqual
(
'
POS
T'
);
expect
(
$
.
ajax
.
calls
.
mostRecent
().
args
[
0
].
data
).
toEqual
({
'problem_to_reset'
:
location
,
'unique_student_identifier'
:
'userman'
,
...
...
@@ -130,7 +130,7 @@ define([
spyOn
(
$
,
'ajax'
);
StaffDebug
.
rescore
(
locationName
,
location
);
expect
(
$
.
ajax
.
calls
.
mostRecent
().
args
[
0
].
type
).
toEqual
(
'
GE
T'
);
expect
(
$
.
ajax
.
calls
.
mostRecent
().
args
[
0
].
type
).
toEqual
(
'
POS
T'
);
expect
(
$
.
ajax
.
calls
.
mostRecent
().
args
[
0
].
data
).
toEqual
({
'problem_to_reset'
:
location
,
'unique_student_identifier'
:
'userman'
,
...
...
This diff is collapsed.
Click to expand it.
lms/static/js/staff_debug_actions.js
View file @
d54f79f5
...
...
@@ -31,7 +31,7 @@ var StaffDebug = (function (){
'delete_module'
:
action
.
delete_module
};
$
.
ajax
({
type
:
"
GE
T"
,
type
:
"
POS
T"
,
url
:
get_url
(
action
.
method
),
data
:
pdata
,
success
:
function
(
data
){
...
...
This diff is collapsed.
Click to expand it.
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