Commit 297206f2 by Brian Wilson

Add background task history to instructor dash (as table).

Add task-history-per-student button, and fix display of task messages.
parent 6cd23875
......@@ -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'
......
......@@ -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)
......
......@@ -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 += "<font color='red'>Couldn't find student with that email or username. </font>"
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 += '<font color="red">Failed to create a background task for resetting "{0}": {1}.</font>'.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 += "<font color='red'>Couldn't find student with that email or username. </font>"
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 += "<font color='red'>Couldn't find module with that urlname. </font>"
......@@ -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 += "<a href='{0}' target='_blank'> Progress page for username: {1} with email address: {2}</a>.".format(progress_url, student_to_reset.username, student_to_reset.email)
except User.DoesNotExist:
msg += "<font color='red'>Couldn't find student with that username. </font>"
msg += "<a href='{0}' target='_blank'> Progress page for username: {1} with email address: {2}</a>.".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 = '<font color="red">Failed to find any background tasks for course "{course}", module "{problem}" and student "{student}".</font>'
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 += '<font color="red">Failed to find any background tasks for course "{course}" and module "{problem}".</font>'.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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment