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
Jun 06, 2016
by
Robert Raposa
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Switch dashboard from GET to POST.
parent
58856964
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
398 additions
and
233 deletions
+398
-233
lms/djangoapps/instructor/tests/test_api.py
+261
-126
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
})
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
):
...
...
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
...
...
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
=>
...
...
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
...
...
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
...
...
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
)
=>
...
...
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
...
...
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
)
=>
...
...
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
)
{
...
...
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
,{});
...
...
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'
,
...
...
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
){
...
...
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