Remove Grade Adjustment code from legacy dash
This commit is contained in:
@@ -1,70 +0,0 @@
|
||||
"""
|
||||
View-level tests for resetting student state in legacy instructor dash.
|
||||
"""
|
||||
|
||||
import json
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from courseware.tests.helpers import LoginEnrollmentTestCase
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.django_utils import TEST_DATA_MOCK_MODULESTORE
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from student.tests.factories import UserFactory, AdminFactory, CourseEnrollmentFactory
|
||||
|
||||
from courseware.models import StudentModule
|
||||
|
||||
from submissions import api as sub_api
|
||||
from student.models import anonymous_id_for_user
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MOCK_MODULESTORE)
|
||||
class InstructorResetStudentStateTest(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
"""
|
||||
Reset student state from the legacy instructor dash.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Log in as an instructor, and create a course/student to reset.
|
||||
"""
|
||||
instructor = AdminFactory.create()
|
||||
self.client.login(username=instructor.username, password='test')
|
||||
self.student = UserFactory.create(username='test', email='test@example.com')
|
||||
self.course = CourseFactory.create()
|
||||
CourseEnrollmentFactory.create(user=self.student, course_id=self.course.id)
|
||||
|
||||
def test_delete_student_state_resets_scores(self):
|
||||
problem_location = self.course.id.make_usage_key('dummy', 'module')
|
||||
|
||||
# Create a student module for the user
|
||||
StudentModule.objects.create(
|
||||
student=self.student,
|
||||
course_id=self.course.id,
|
||||
module_state_key=problem_location,
|
||||
state=json.dumps({})
|
||||
)
|
||||
|
||||
# Create a submission and score for the student using the submissions API
|
||||
student_item = {
|
||||
'student_id': anonymous_id_for_user(self.student, self.course.id),
|
||||
'course_id': self.course.id.to_deprecated_string(),
|
||||
'item_id': problem_location.to_deprecated_string(),
|
||||
'item_type': 'openassessment'
|
||||
}
|
||||
submission = sub_api.create_submission(student_item, 'test answer')
|
||||
sub_api.set_score(submission['uuid'], 1, 2)
|
||||
|
||||
# Delete student state using the instructor dash
|
||||
url = reverse('instructor_dashboard_legacy', kwargs={'course_id': self.course.id.to_deprecated_string()})
|
||||
response = self.client.post(url, {
|
||||
'action': 'Delete student state for module',
|
||||
'unique_student_identifier': self.student.email,
|
||||
'problem_for_student': problem_location.to_deprecated_string(),
|
||||
})
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Verify that the student's scores have been reset in the submissions API
|
||||
score = sub_api.get_score(student_item)
|
||||
self.assertIs(score, None)
|
||||
@@ -259,274 +259,6 @@ def instructor_dashboard(request, course_id):
|
||||
track.views.server_track(request, "dump-graded-assignments-config", {}, page="idashboard")
|
||||
msg += dump_grading_context(course)
|
||||
|
||||
elif "Rescore ALL students' problem submissions" in action:
|
||||
problem_location_str = strip_if_string(request.POST.get('problem_for_all_students', ''))
|
||||
try:
|
||||
problem_location = course_key.make_usage_key_from_deprecated_string(problem_location_str)
|
||||
instructor_task = submit_rescore_problem_for_all_students(request, problem_location)
|
||||
if instructor_task is None:
|
||||
msg += '<font color="red">{text}</font>'.format(
|
||||
text=_('Failed to create a background task for rescoring "{problem_url}".').format(
|
||||
problem_url=problem_location_str
|
||||
)
|
||||
)
|
||||
else:
|
||||
track.views.server_track(
|
||||
request,
|
||||
"rescore-all-submissions",
|
||||
{
|
||||
"problem": problem_location_str,
|
||||
"course": course_key.to_deprecated_string()
|
||||
},
|
||||
page="idashboard"
|
||||
)
|
||||
|
||||
except (InvalidKeyError, ItemNotFoundError) as err:
|
||||
msg += '<font color="red">{text}</font>'.format(
|
||||
text=_('Failed to create a background task for rescoring "{problem_url}": problem not found.').format(
|
||||
problem_url=problem_location_str
|
||||
)
|
||||
)
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
log.error("Encountered exception from rescore: {0}".format(err))
|
||||
msg += '<font color="red">{text}</font>'.format(
|
||||
text=_('Failed to create a background task for rescoring "{url}": {message}.').format(
|
||||
url=problem_location_str, message=err.message
|
||||
)
|
||||
)
|
||||
|
||||
elif "Reset ALL students' attempts" in action:
|
||||
problem_location_str = strip_if_string(request.POST.get('problem_for_all_students', ''))
|
||||
try:
|
||||
problem_location = course_key.make_usage_key_from_deprecated_string(problem_location_str)
|
||||
instructor_task = submit_reset_problem_attempts_for_all_students(request, problem_location)
|
||||
if instructor_task is None:
|
||||
msg += '<font color="red">{text}</font>'.format(
|
||||
text=_('Failed to create a background task for resetting "{problem_url}".').format(problem_url=problem_location_str)
|
||||
)
|
||||
else:
|
||||
track.views.server_track(
|
||||
request,
|
||||
"reset-all-attempts",
|
||||
{
|
||||
"problem": problem_location_str,
|
||||
"course": course_key.to_deprecated_string()
|
||||
},
|
||||
page="idashboard"
|
||||
)
|
||||
except (InvalidKeyError, ItemNotFoundError) as err:
|
||||
log.error('Failure to reset: unknown problem "{0}"'.format(err))
|
||||
msg += '<font color="red">{text}</font>'.format(
|
||||
text=_('Failed to create a background task for resetting "{problem_url}": problem not found.').format(
|
||||
problem_url=problem_location_str
|
||||
)
|
||||
)
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
log.error("Encountered exception from reset: {0}".format(err))
|
||||
msg += '<font color="red">{text}</font>'.format(
|
||||
text=_('Failed to create a background task for resetting "{url}": {message}.').format(
|
||||
url=problem_location_str, message=err.message
|
||||
)
|
||||
)
|
||||
|
||||
elif "Show Background Task History for Student" in action:
|
||||
# put this before the non-student case, since the use of "in" will cause this to be missed
|
||||
unique_student_identifier = request.POST.get('unique_student_identifier', '')
|
||||
message, student = get_student_from_identifier(unique_student_identifier)
|
||||
if student is None:
|
||||
msg += message
|
||||
else:
|
||||
problem_location_str = strip_if_string(request.POST.get('problem_for_student', ''))
|
||||
try:
|
||||
problem_location = course_key.make_usage_key_from_deprecated_string(problem_location_str)
|
||||
except InvalidKeyError:
|
||||
msg += '<font color="red">{text}</font>'.format(
|
||||
text=_('Could not find problem location "{url}".').format(
|
||||
url=problem_location_str
|
||||
)
|
||||
)
|
||||
else:
|
||||
message, datatable = get_background_task_table(course_key, problem_location, student)
|
||||
msg += message
|
||||
|
||||
elif "Show Background Task History" in action:
|
||||
problem_location_str = strip_if_string(request.POST.get('problem_for_all_students', ''))
|
||||
try:
|
||||
problem_location = course_key.make_usage_key_from_deprecated_string(problem_location_str)
|
||||
except InvalidKeyError:
|
||||
msg += '<font color="red">{text}</font>'.format(
|
||||
text=_('Could not find problem location "{url}".').format(
|
||||
url=problem_location_str
|
||||
)
|
||||
)
|
||||
else:
|
||||
message, datatable = get_background_task_table(course_key, problem_location)
|
||||
msg += message
|
||||
|
||||
elif ("Reset student's attempts" in action or
|
||||
"Delete student state for module" in action or
|
||||
"Rescore student's problem submission" in action):
|
||||
# get the form data
|
||||
unique_student_identifier = request.POST.get(
|
||||
'unique_student_identifier', ''
|
||||
)
|
||||
problem_location_str = strip_if_string(request.POST.get('problem_for_student', ''))
|
||||
try:
|
||||
module_state_key = course_key.make_usage_key_from_deprecated_string(problem_location_str)
|
||||
except InvalidKeyError:
|
||||
msg += '<font color="red">{text}</font>'.format(
|
||||
text=_('Could not find problem location "{url}".').format(
|
||||
url=problem_location_str
|
||||
)
|
||||
)
|
||||
else:
|
||||
# try to uniquely id student by email address or username
|
||||
message, student = get_student_from_identifier(unique_student_identifier)
|
||||
msg += message
|
||||
student_module = None
|
||||
if student is not None:
|
||||
# Reset the student's score in the submissions API
|
||||
# Currently this is used only by open assessment (ORA 2)
|
||||
# We need to do this *before* retrieving the `StudentModule` model,
|
||||
# because it's possible for a score to exist even if no student module exists.
|
||||
if "Delete student state for module" in action:
|
||||
try:
|
||||
sub_api.reset_score(
|
||||
anonymous_id_for_user(student, course_key),
|
||||
course_key.to_deprecated_string(),
|
||||
module_state_key.to_deprecated_string(),
|
||||
)
|
||||
except sub_api.SubmissionError:
|
||||
# Trust the submissions API to log the error
|
||||
error_msg = _("An error occurred while deleting the score.")
|
||||
msg += "<font color='red'>{err}</font> ".format(err=error_msg)
|
||||
|
||||
# find the module in question
|
||||
try:
|
||||
student_module = StudentModule.objects.get(
|
||||
student_id=student.id,
|
||||
course_id=course_key,
|
||||
module_state_key=module_state_key
|
||||
)
|
||||
msg += _("Found module. ")
|
||||
|
||||
except StudentModule.DoesNotExist as err:
|
||||
error_msg = _("Couldn't find module with that urlname: {url}. ").format(url=problem_location_str)
|
||||
msg += "<font color='red'>{err_msg} ({err})</font>".format(err_msg=error_msg, err=err)
|
||||
log.debug(error_msg)
|
||||
|
||||
if student_module is not None:
|
||||
if "Delete student state for module" in action:
|
||||
# delete the state
|
||||
try:
|
||||
student_module.delete()
|
||||
|
||||
msg += "<font color='red'>{text}</font>".format(
|
||||
text=_("Deleted student module state for {state}!").format(state=module_state_key)
|
||||
)
|
||||
event = {
|
||||
"problem": problem_location_str,
|
||||
"student": unique_student_identifier,
|
||||
"course": course_key.to_deprecated_string()
|
||||
}
|
||||
track.views.server_track(
|
||||
request,
|
||||
"delete-student-module-state",
|
||||
event,
|
||||
page="idashboard"
|
||||
)
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
error_msg = _("Failed to delete module state for {id}/{url}. ").format(
|
||||
id=unique_student_identifier, url=problem_location_str
|
||||
)
|
||||
msg += "<font color='red'>{err_msg} ({err})</font>".format(err_msg=error_msg, err=err)
|
||||
log.exception(error_msg)
|
||||
elif "Reset student's attempts" in action:
|
||||
# modify the problem's state
|
||||
try:
|
||||
# load the state json
|
||||
problem_state = json.loads(student_module.state)
|
||||
old_number_of_attempts = problem_state["attempts"]
|
||||
problem_state["attempts"] = 0
|
||||
# save
|
||||
student_module.state = json.dumps(problem_state)
|
||||
student_module.save()
|
||||
event = {
|
||||
"old_attempts": old_number_of_attempts,
|
||||
"student": unicode(student),
|
||||
"problem": student_module.module_state_key,
|
||||
"instructor": unicode(request.user),
|
||||
"course": course_key.to_deprecated_string()
|
||||
}
|
||||
track.views.server_track(request, "reset-student-attempts", event, page="idashboard")
|
||||
msg += "<font color='green'>{text}</font>".format(
|
||||
text=_("Module state successfully reset!")
|
||||
)
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
error_msg = _("Couldn't reset module state for {id}/{url}. ").format(
|
||||
id=unique_student_identifier, url=problem_location_str
|
||||
)
|
||||
msg += "<font color='red'>{err_msg} ({err})</font>".format(err_msg=error_msg, err=err)
|
||||
log.exception(error_msg)
|
||||
else:
|
||||
# "Rescore student's problem submission" case
|
||||
try:
|
||||
instructor_task = submit_rescore_problem_for_student(request, module_state_key, student)
|
||||
if instructor_task is None:
|
||||
msg += '<font color="red">{text}</font>'.format(
|
||||
text=_('Failed to create a background task for rescoring "{key}" for student {id}.').format(
|
||||
key=module_state_key, id=unique_student_identifier
|
||||
)
|
||||
)
|
||||
else:
|
||||
track.views.server_track(
|
||||
request,
|
||||
"rescore-student-submission",
|
||||
{
|
||||
"problem": module_state_key,
|
||||
"student": unique_student_identifier,
|
||||
"course": course_key.to_deprecated_string()
|
||||
},
|
||||
page="idashboard"
|
||||
)
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
msg += '<font color="red">{text}</font>'.format(
|
||||
text=_('Failed to create a background task for rescoring "{key}": {id}.').format(
|
||||
key=module_state_key, id=err.message
|
||||
)
|
||||
)
|
||||
log.exception("Encountered exception from rescore: student '{0}' problem '{1}'".format(
|
||||
unique_student_identifier, module_state_key
|
||||
)
|
||||
)
|
||||
|
||||
elif "Get link to student's progress page" in action:
|
||||
unique_student_identifier = request.POST.get('unique_student_identifier', '')
|
||||
# try to uniquely id student by email address or username
|
||||
message, student = get_student_from_identifier(unique_student_identifier)
|
||||
msg += message
|
||||
if student is not None:
|
||||
progress_url = reverse('student_progress', kwargs={
|
||||
'course_id': course_key.to_deprecated_string(),
|
||||
'student_id': student.id
|
||||
})
|
||||
track.views.server_track(
|
||||
request,
|
||||
"get-student-progress-page",
|
||||
{
|
||||
"student": unicode(student),
|
||||
"instructor": unicode(request.user),
|
||||
"course": course_key.to_deprecated_string()
|
||||
},
|
||||
page="idashboard"
|
||||
)
|
||||
msg += "<a href='{url}' target='_blank'>{text}</a>.".format(
|
||||
url=progress_url,
|
||||
text=_("Progress page for username: {username} with email address: {email}").format(
|
||||
username=student.username, email=student.email
|
||||
)
|
||||
)
|
||||
|
||||
#----------------------------------------
|
||||
# export grades to remote gradebook
|
||||
|
||||
|
||||
@@ -263,67 +263,13 @@ function goto( mode)
|
||||
%if settings.FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS'):
|
||||
<H2>${_("Course-specific grade adjustment")}</h2>
|
||||
|
||||
<p>
|
||||
${_("Specify a problem in the course here with its complete location:")}
|
||||
<input type="text" name="problem_for_all_students" size="60">
|
||||
</p>
|
||||
## Translators: A location (string of text) follows this sentence.
|
||||
<p>${_("You must provide the complete location of the problem. In the Staff Debug viewer, the location looks like this:")}<br/>
|
||||
<tt>i4x://edX/Open_DemoX/problem/78c98390884243b89f6023745231c525</tt></p>
|
||||
<p>
|
||||
${_("Then select an action:")}
|
||||
<input type="submit" name="action" value="Reset ALL students' attempts">
|
||||
<input type="submit" name="action" value="Rescore ALL students' problem submissions">
|
||||
</p>
|
||||
<p>
|
||||
<p>${_("These actions run in the background, and status for active tasks will appear in a table below. To see status for all tasks submitted for this problem, click on this button:")}
|
||||
</p>
|
||||
<p>
|
||||
<input type="submit" name="action" value="Show Background Task History">
|
||||
</p>
|
||||
<p>${_("To perform these actions, please visit the 'Student Admin' section of the instructor dashboard.")}</p>
|
||||
|
||||
<hr width="40%" style="align:left">
|
||||
%endif
|
||||
|
||||
<h2>${_("Student-specific grade inspection and adjustment")}</h2>
|
||||
<p>
|
||||
${_("Specify the {platform_name} email address or username of a student here:").format(platform_name=settings.PLATFORM_NAME)}
|
||||
<input type="text" name="unique_student_identifier">
|
||||
</p>
|
||||
<p>
|
||||
${_("Click this, and a link to student's progress page will appear below:")}
|
||||
<input type="submit" name="action" value="Get link to student's progress page">
|
||||
</p>
|
||||
<p>
|
||||
${_("Specify a problem in the course here with its complete location:")}
|
||||
<input type="text" name="problem_for_student" size="60">
|
||||
</p>
|
||||
## Translators: A location (string of text) follows this sentence.
|
||||
<p>${_("You must provide the complete location of the problem. In the Staff Debug viewer, the location looks like this:")}<br/>
|
||||
<tt>i4x://edX/Open_DemoX/problem/78c98390884243b89f6023745231c525</tt></p>
|
||||
|
||||
<p>
|
||||
${_("Then select an action:")}
|
||||
<input type="submit" name="action" value="Reset student's attempts">
|
||||
%if settings.FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS'):
|
||||
<input type="submit" name="action" value="Rescore student's problem submission">
|
||||
%endif
|
||||
</p>
|
||||
|
||||
%if instructor_access:
|
||||
<p>
|
||||
${_("You may also delete the entire state of a student for the specified module:")}
|
||||
<input type="submit" name="action" value="Delete student state for module">
|
||||
</p>
|
||||
%endif
|
||||
%if settings.FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS'):
|
||||
<p>${_("Rescoring runs in the background, and status for active tasks will appear in a table below. "
|
||||
"To see status for all tasks submitted for this problem and student, click on this button:")}
|
||||
</p>
|
||||
<p>
|
||||
<input type="submit" name="action" value="Show Background Task History for Student">
|
||||
</p>
|
||||
%endif
|
||||
<p>${_("To perform these actions, please visit the 'Student Admin' section of the instructor dashboard.")}</p>
|
||||
|
||||
%endif
|
||||
|
||||
|
||||
Reference in New Issue
Block a user