diff --git a/lms/djangoapps/courseware/task_queue.py b/lms/djangoapps/courseware/task_queue.py
index 90cdd7f765..85649c29f2 100644
--- a/lms/djangoapps/courseware/task_queue.py
+++ b/lms/djangoapps/courseware/task_queue.py
@@ -143,6 +143,8 @@ def _update_course_task_log(course_task_log_entry, task_result):
Calculates json to store in task_progress field.
"""
+ # Just pull values out of the result object once. If we check them later,
+ # the state and result may have changed.
task_id = task_result.task_id
result_state = task_result.state
returned_result = task_result.result
@@ -240,39 +242,36 @@ def _get_course_task_log_status(task_id):
# define ajax return value:
output = {}
- # if the task is already known to be done, then there's no reason to query
+ # if the task is not already known to be done, then we need to query
# the underlying task's result object:
if course_task_log_entry.task_state not in READY_STATES:
- # we need to get information from the task result directly now.
-
- # Just create the result object, and pull values out once.
- # (If we check them later, the state and result may have changed.)
result = AsyncResult(task_id)
output.update(_update_course_task_log(course_task_log_entry, result))
elif course_task_log_entry.task_progress is not None:
# task is already known to have finished, but report on its status:
output['task_progress'] = json.loads(course_task_log_entry.task_progress)
- if course_task_log_entry.task_state == 'FAILURE':
- output['message'] = output['task_progress']['message']
# output basic information matching what's stored in CourseTaskLog:
output['task_id'] = course_task_log_entry.task_id
output['task_state'] = course_task_log_entry.task_state
output['in_progress'] = course_task_log_entry.task_state not in READY_STATES
- if course_task_log_entry.task_state == 'SUCCESS':
- succeeded, message = _get_task_completion_message(course_task_log_entry)
+ if course_task_log_entry.task_state in READY_STATES:
+ succeeded, message = get_task_completion_message(course_task_log_entry)
output['message'] = message
output['succeeded'] = succeeded
return output
-def _get_task_completion_message(course_task_log_entry):
+def get_task_completion_message(course_task_log_entry):
"""
Construct progress message from progress information in CourseTaskLog entry.
Returns (boolean, message string) duple.
+
+ Used for providing messages to course_task_log_status(), as well as
+ external calls for providing course task submission history information.
"""
succeeded = False
@@ -281,30 +280,36 @@ def _get_task_completion_message(course_task_log_entry):
return (succeeded, "No status information available")
task_progress = json.loads(course_task_log_entry.task_progress)
+ if course_task_log_entry.task_state in ['FAILURE', 'REVOKED']:
+ return(succeeded, task_progress['message'])
+
action_name = task_progress['action_name']
num_attempted = task_progress['attempted']
num_updated = task_progress['updated']
- # num_total = task_progress['total']
+ num_total = task_progress['total']
if course_task_log_entry.student is not None:
if num_attempted == 0:
- msg = "Unable to find submission to be {action} for student '{student}' and problem '{problem}'."
+ msg = "Unable to find submission to be {action} for student '{student}'"
elif num_updated == 0:
- msg = "Problem failed to be {action} for student '{student}' and problem '{problem}'"
+ msg = "Problem failed to be {action} for student '{student}'"
else:
succeeded = True
- msg = "Problem successfully {action} for student '{student}' and problem '{problem}'"
+ msg = "Problem successfully {action} for student '{student}'"
elif num_attempted == 0:
- msg = "Unable to find any students with submissions to be {action} for problem '{problem}'."
+ msg = "Unable to find any students with submissions to be {action}"
elif num_updated == 0:
- msg = "Problem failed to be {action} for any of {attempted} students for problem '{problem}'"
+ msg = "Problem failed to be {action} for any of {attempted} students"
elif num_updated == num_attempted:
succeeded = True
- msg = "Problem successfully {action} for {attempted} students for problem '{problem}'"
+ msg = "Problem successfully {action} for {attempted} students"
elif num_updated < num_attempted:
- msg = "Problem {action} for {updated} of {attempted} students for problem '{problem}'"
+ msg = "Problem {action} for {updated} of {attempted} students"
+
+ if course_task_log_entry.student is not None and num_attempted != num_total:
+ msg += " (out of {total})"
# Update status in task result object itself:
- message = msg.format(action=action_name, updated=num_updated, attempted=num_attempted,
+ message = msg.format(action=action_name, updated=num_updated, attempted=num_attempted, total=num_total,
student=course_task_log_entry.student, problem=course_task_log_entry.task_args)
return (succeeded, message)
@@ -343,7 +348,7 @@ def submit_regrade_problem_for_student(request, course_id, problem_url, student)
An exception is thrown if the problem doesn't exist, or if the particular
problem is already being regraded for this student.
"""
- # check arguments: let exceptions return up to the caller.
+ # check arguments: let exceptions return up to the caller.
_check_arguments_for_regrading(course_id, problem_url)
task_name = 'regrade_problem'
diff --git a/lms/djangoapps/courseware/tests/test_task_queue.py b/lms/djangoapps/courseware/tests/test_task_queue.py
index 3a20fb237d..c1ae1925e1 100644
--- a/lms/djangoapps/courseware/tests/test_task_queue.py
+++ b/lms/djangoapps/courseware/tests/test_task_queue.py
@@ -225,7 +225,7 @@ class TaskQueueTestCase(TestCase):
self.assertFalse(output['succeeded'])
_, output = self._get_output_for_task_success(10, 0, 10)
- self.assertTrue("Problem failed to be regraded for any of 10 students " in output['message'])
+ self.assertTrue("Problem failed to be regraded for any of 10 students" in output['message'])
self.assertFalse(output['succeeded'])
_, output = self._get_output_for_task_success(10, 8, 10)
diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py
index 6698635d9a..cde47c4b7a 100644
--- a/lms/djangoapps/instructor/views.py
+++ b/lms/djangoapps/instructor/views.py
@@ -19,9 +19,12 @@ from django.contrib.auth.models import User, Group
from django.http import HttpResponse
from django_future.csrf import ensure_csrf_cookie
from django.views.decorators.cache import cache_control
-from mitxmako.shortcuts import render_to_response
from django.core.urlresolvers import reverse
+import xmodule.graders as xmgraders
+from xmodule.modulestore.django import modulestore
+from xmodule.modulestore.exceptions import ItemNotFoundError
+
from courseware import grades
from courseware import task_queue
from courseware.access import (has_access, get_access_group_name,
@@ -33,14 +36,12 @@ from django_comment_common.models import (Role,
FORUM_ROLE_MODERATOR,
FORUM_ROLE_COMMUNITY_TA)
from django_comment_client.utils import has_forum_access
+from instructor.offline_gradecalc import student_grades, offline_grades_available
+from mitxmako.shortcuts import render_to_response
from psychometrics import psychoanalyze
from student.models import CourseEnrollment, CourseEnrollmentAllowed
-from xmodule.modulestore.django import modulestore
-import xmodule.graders as xmgraders
import track.views
-from .offline_gradecalc import student_grades, offline_grades_available
-from xmodule.modulestore.exceptions import ItemNotFoundError
log = logging.getLogger(__name__)
@@ -156,6 +157,20 @@ def instructor_dashboard(request, course_id):
(org, course_name, _) = course_id.split("/")
return "i4x://" + org + "/" + course_name + "/" + urlname
+ def get_student_from_identifier(unique_student_identifier):
+ # try to uniquely id student by email address or username
+ msg = ""
+ try:
+ if "@" in unique_student_identifier:
+ student = User.objects.get(email=unique_student_identifier)
+ else:
+ student = User.objects.get(username=unique_student_identifier)
+ msg += "Found a single student. "
+ except User.DoesNotExist:
+ student = None
+ msg += "Couldn't find student with that email or username. "
+ return msg, student
+
# process actions from form POST
action = request.POST.get('action', '')
use_offline = request.POST.get('use_offline_grades', False)
@@ -259,31 +274,49 @@ def instructor_dashboard(request, course_id):
log.error("Encountered exception from reset: {0}".format(e))
msg += 'Failed to create a background task for resetting "{0}": {1}.'.format(problem_url, e.message)
- elif "Reset student's attempts" in action or "Delete student state for module" in action \
+ 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_urlname = request.POST.get('problem_for_student', '')
+ problem_url = get_module_url(problem_urlname)
+ message, task_datatable = get_background_task_table(course_id, problem_url, student)
+ msg += message
+ if task_datatable is not None:
+ datatable = task_datatable
+ datatable['title'] = "{course_id} > {location} > {student}".format(course_id=course_id,
+ location=problem_url,
+ student=student.username)
+
+ elif "Show Background Task History" in action:
+ problem_urlname = request.POST.get('problem_for_all_students', '')
+ problem_url = get_module_url(problem_urlname)
+ message, task_datatable = get_background_task_table(course_id, problem_url)
+ msg += message
+ if task_datatable is not None:
+ datatable = task_datatable
+ datatable['title'] = "{course_id} > {location}".format(course_id=course_id, location=problem_url)
+
+ elif "Reset student's attempts" in action \
+ or "Delete student state for module" in action \
or "Regrade student's problem submission" in action:
# get the form data
unique_student_identifier = request.POST.get('unique_student_identifier', '')
problem_urlname = request.POST.get('problem_for_student', '')
module_state_key = get_module_url(problem_urlname)
-
# try to uniquely id student by email address or username
- try:
- if "@" in unique_student_identifier:
- student = User.objects.get(email=unique_student_identifier)
- else:
- student = User.objects.get(username=unique_student_identifier)
- msg += "Found a single student. "
- except User.DoesNotExist:
- student = None
- msg += "Couldn't find student with that email or username. "
-
+ message, student = get_student_from_identifier(unique_student_identifier)
+ msg += message
student_module = None
if student is not None:
# find the module in question
try:
student_module = StudentModule.objects.get(student_id=student.id,
- course_id=course_id,
- module_state_key=module_state_key)
+ course_id=course_id,
+ module_state_key=module_state_key)
msg += "Found module. "
except StudentModule.DoesNotExist:
msg += "Couldn't find module with that urlname. "
@@ -336,22 +369,19 @@ def instructor_dashboard(request, course_id):
elif "Get link to student's progress page" in action:
unique_student_identifier = request.POST.get('unique_student_identifier', '')
- try:
- if "@" in unique_student_identifier:
- student_to_reset = User.objects.get(email=unique_student_identifier)
- else:
- student_to_reset = User.objects.get(username=unique_student_identifier)
- progress_url = reverse('student_progress', kwargs={'course_id': course_id, 'student_id': student_to_reset.id})
+ # 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_id, 'student_id': student.id})
track.views.server_track(request,
'{instructor} requested progress page for {student} in {course}'.format(
- student=student_to_reset,
+ student=student,
instructor=request.user,
course=course_id),
{},
page='idashboard')
- msg += " Progress page for username: {1} with email address: {2}.".format(progress_url, student_to_reset.username, student_to_reset.email)
- except User.DoesNotExist:
- msg += "Couldn't find student with that username. "
+ msg += " Progress page for username: {1} with email address: {2}.".format(progress_url, student.username, student.email)
#----------------------------------------
# export grades to remote gradebook
@@ -492,7 +522,7 @@ def instructor_dashboard(request, course_id):
if problem_to_dump[-4:] == ".xml":
problem_to_dump = problem_to_dump[:-4]
try:
- (org, course_name, run) = course_id.split("/")
+ (org, course_name, _) = course_id.split("/")
module_state_key = "i4x://" + org + "/" + course_name + "/problem/" + problem_to_dump
smdat = StudentModule.objects.filter(course_id=course_id,
module_state_key=module_state_key)
@@ -1251,99 +1281,56 @@ def dump_grading_context(course):
return msg
-#def old1testcelery(request):
-# """
-# A Simple view that checks if the application can talk to the celery workers
-# """
-# args = ('ping',)
-# result = tasks.echo.apply_async(args, retry=False)
-# value = result.get(timeout=0.5)
-# output = {
-# 'task_id': result.id,
-# 'value': value
-# }
-# return HttpResponse(json.dumps(output, indent=4))
-#
-#
-#def old2testcelery(request):
-# """
-# A Simple view that checks if the application can talk to the celery workers
-# """
-# args = (10,)
-# result = tasks.waitawhile.apply_async(args, retry=False)
-# while not result.ready():
-# sleep(0.5) # in seconds
-# if result.state == "PROGRESS":
-# if hasattr(result, 'result') and 'current' in result.result:
-# log.info("still waiting... progress at {0} of {1}".format(result.result['current'], result.result['total']))
-# else:
-# log.info("still making progress... ")
-# if result.successful():
-# value = result.result
-# output = {
-# 'task_id': result.id,
-# 'value': value
-# }
-# return HttpResponse(json.dumps(output, indent=4))
-#
-#
-#def testcelery(request):
-# """
-# A Simple view that checks if the application can talk to the celery workers
-# """
-# args = (10,)
-# result = tasks.waitawhile.apply_async(args, retry=False)
-# task_id = result.id
-# # return the task_id to a template which will set up an ajax call to
-# # check the progress of the task.
-# return testcelery_status(request, task_id)
-## return mitxmako.shortcuts.render_to_response('celery_ajax.html', {
-## 'element_id': 'celery_task'
-## 'id': self.task_id,
-## 'ajax_url': reverse('testcelery_ajax'),
-## })
-#
-#
-#def testcelery_status(request, task_id):
-# result = tasks.waitawhile.AsyncResult(task_id)
-# while not result.ready():
-# sleep(0.5) # in seconds
-# if result.state == "PROGRESS":
-# if hasattr(result, 'result') and 'current' in result.result:
-# log.info("still waiting... progress at {0} of {1}".format(result.result['current'], result.result['total']))
-# else:
-# log.info("still making progress... ")
-# if result.successful():
-# value = result.result
-# output = {
-# 'task_id': result.id,
-# 'value': value
-# }
-# return HttpResponse(json.dumps(output, indent=4))
-#
-#
-#def celery_task_status(request, task_id):
-# # TODO: determine if we need to know the name of the original task,
-# # or if this could be any task... Sample code seems to indicate that
-# # we could just include the AsyncResult class directly, i.e.:
-# # from celery.result import AsyncResult.
-# result = tasks.waitawhile.AsyncResult(task_id)
-#
-# output = {
-# 'task_id': result.id,
-# 'state': result.state
-# }
-#
-# if result.state == "PROGRESS":
-# if hasattr(result, 'result') and 'current' in result.result:
-# log.info("still waiting... progress at {0} of {1}".format(result.result['current'], result.result['total']))
-# output['current'] = result.result['current']
-# output['total'] = result.result['total']
-# else:
-# log.info("still making progress... ")
-#
-# if result.successful():
-# value = result.result
-# output['value'] = value
-#
-# return HttpResponse(json.dumps(output, indent=4))
+def get_background_task_table(course_id, problem_url, student=None):
+ course_tasks = CourseTaskLog.objects.filter(course_id=course_id, task_args=problem_url)
+ if student is not None:
+ course_tasks = course_tasks.filter(student=student)
+
+ history_entries = course_tasks.order_by('-id')
+ datatable = None
+ msg = ""
+ # first check to see if there is any history at all
+ # (note that we don't have to check that the arguments are valid; it
+ # just won't find any entries.)
+ if (len(history_entries)) == 0:
+ if student is not None:
+ log.debug("Found no background tasks for request: {course}, {problem}, and student {student}".format(course=course_id, problem=problem_url, student=student.username))
+ template = 'Failed to find any background tasks for course "{course}", module "{problem}" and student "{student}".'
+ msg += template.format(course=course_id, problem=problem_url, student=student.username)
+ else:
+ log.debug("Found no background tasks for request: {course}, {problem}".format(course=course_id, problem=problem_url))
+ msg += 'Failed to find any background tasks for course "{course}" and module "{problem}".'.format(course=course_id, problem=problem_url)
+ else:
+ datatable = {}
+ datatable['header'] = ["Order",
+ "Task Name",
+ "Student",
+ "Task Id",
+ "Requester",
+ "Submitted",
+ "Updated",
+ "Task State",
+ "Task Status",
+ "Message"]
+
+ datatable['data'] = []
+ for i, course_task in enumerate(history_entries):
+ success, message = task_queue.get_task_completion_message(course_task)
+ if success:
+ status = "Complete"
+ else:
+ status = "Incomplete"
+ row = ["#{0}".format(len(history_entries) - i),
+ str(course_task.task_name),
+ str(course_task.student),
+ str(course_task.task_id),
+ str(course_task.requester),
+ course_task.created.strftime("%Y/%m/%d %H:%M:%S"),
+ course_task.updated.strftime("%Y/%m/%d %H:%M:%S"),
+ str(course_task.task_state),
+ status,
+ message]
+ datatable['data'].append(row)
+
+ return msg, datatable
+