diff --git a/lms/djangoapps/courseware/tasks.py b/lms/djangoapps/courseware/tasks.py
index 516997485e..674ea1effc 100644
--- a/lms/djangoapps/courseware/tasks.py
+++ b/lms/djangoapps/courseware/tasks.py
@@ -1,43 +1,31 @@
import json
import logging
+from time import sleep
from django.contrib.auth.models import User
+import mitxmako.middleware as middleware
+from django.http import HttpResponse
+# from django.http import HttpRequest
+from django.test.client import RequestFactory
+
+from celery import task, current_task
+from celery.result import AsyncResult
+from celery.utils.log import get_task_logger
+
from courseware.models import StudentModule, CourseTaskLog
from courseware.model_data import ModelDataCache
from courseware.module_render import get_module
from xmodule.modulestore.django import modulestore
-from xmodule.modulestore.exceptions import ItemNotFoundError,\
- InvalidLocationError
+from xmodule.modulestore.exceptions import ItemNotFoundError, InvalidLocationError
import track.views
-from celery import task, current_task
-from celery.utils.log import get_task_logger
-from time import sleep
-from django.core.handlers.wsgi import WSGIRequest
+# define different loggers for use within tasks and on client side
logger = get_task_logger(__name__)
-
-# celery = Celery('tasks', broker='django://')
-
log = logging.getLogger(__name__)
-@task
-def add(x, y):
- return x + y
-
-
-@task
-def echo(value):
- if value == 'ping':
- result = 'pong'
- else:
- result = 'got: {0}'.format(value)
-
- return result
-
-
@task
def waitawhile(value):
for i in range(value):
@@ -66,6 +54,15 @@ def _update_problem_module_state(request, course_id, problem_url, student, updat
# complete, as far as celery is concerned, but have an internal status of failed.)
succeeded = False
+ # add hack so that mako templates will work on celery worker server:
+ # The initialization of Make templating is usually done when Django is
+ # initialize middleware packages as part of processing a server request.
+ # When this is run on a celery worker server, no such initialization is
+ # called. So we look for the result: the defining of the lookup paths
+ # for templates.
+ if 'main' not in middleware.lookup:
+ middleware.MakoMiddleware()
+
# find the problem descriptor, if any:
try:
module_descriptor = modulestore().get_instance(course_id, module_state_key)
@@ -105,8 +102,8 @@ def _update_problem_module_state(request, course_id, problem_url, student, updat
# try:
if update_fcn(request, module_to_update, module_descriptor):
num_updated += 1
-# if there's an error, just let it throw, and the task will
-# be marked as FAILED, with a stack trace.
+# if there's an error, just let it throw, and the task will
+# be marked as FAILED, with a stack trace.
# except UpdateProblemModuleStateError as e:
# something bad happened, so exit right away
# return (succeeded, e.message)
@@ -142,7 +139,8 @@ def _update_problem_module_state(request, course_id, problem_url, student, updat
# and update status in course task table as well:
# TODO: figure out how this is legal. The actual task result
# status is updated by celery when this task completes, and is
- # not
+ # presumably going to clobber this custom metadata. So if we want
+ # any such status to persist, we have to write it to the CourseTaskLog instead.
# course_task_log_entry = CourseTaskLog.objects.get(task_id=current_task.id)
# course_task_log_entry.task_status = ...
@@ -216,10 +214,9 @@ def _regrade_problem_module_state(request, module_to_regrade, module_descriptor)
return False
else:
track.views.server_track(request,
- '{instructor} regrade problem {problem} for student {student} '
+ 'regrade problem {problem} for student {student} '
'in {course}'.format(student=student.id,
problem=module_to_regrade.module_state_key,
- instructor=request.user,
course=course_id),
{},
page='idashboard')
@@ -257,8 +254,10 @@ def regrade_problem_for_student(request, course_id, problem_url, student_identif
@task
def _regrade_problem_for_all_students(request_environ, course_id, problem_url):
-# request = dummy_request
- request = WSGIRequest(request_environ)
+# request = HttpRequest()
+# request.META.update(request_environ)
+ factory = RequestFactory(**request_environ)
+ request = factory.get('/')
action_name = 'regraded'
update_fcn = _regrade_problem_module_state
filter_fcn = filter_problem_module_state_for_done
@@ -269,6 +268,7 @@ def _regrade_problem_for_all_students(request_environ, course_id, problem_url):
def regrade_problem_for_all_students(request, course_id, problem_url):
# Figure out (for now) how to serialize what we need of the request. The actual
# request will not successfully serialize with json or with pickle.
+ # Maybe we can just pass all META info as a dict.
request_environ = {'HTTP_USER_AGENT': request.META['HTTP_USER_AGENT'],
'REMOTE_ADDR': request.META['REMOTE_ADDR'],
'SERVER_NAME': request.META['SERVER_NAME'],
@@ -290,6 +290,76 @@ def regrade_problem_for_all_students(request, course_id, problem_url):
return course_task_log
+def course_task_log_status(request, task_id=None):
+ """
+ This returns the status of a course-related task as a JSON-serialized dict.
+ """
+ output = {}
+ if task_id is not None:
+ output = _get_course_task_log_status(task_id)
+ elif 'task_id' in request.POST:
+ task_id = request.POST['task_id']
+ output = _get_course_task_log_status(task_id)
+ elif 'task_ids[]' in request.POST:
+ tasks = request.POST.getlist('task_ids[]')
+ for task_id in tasks:
+ task_output = _get_course_task_log_status(task_id)
+ output[task_id] = task_output
+ # TODO else: raise exception?
+
+ return HttpResponse(json.dumps(output, indent=4))
+
+
+def _get_course_task_log_status(task_id):
+ course_task_log_entry = CourseTaskLog.objects.get(task_id=task_id)
+ # TODO: error handling if it doesn't exist...
+
+ def not_in_progress(entry):
+ # TODO: do better than to copy list from celery.states.READY_STATES
+ return entry.task_status in ['SUCCESS', 'FAILURE', 'REVOKED']
+
+ # if the task is already known to be done, then there's no reason to query
+ # the underlying task:
+ if not_in_progress(course_task_log_entry):
+ output = {
+ 'task_id': course_task_log_entry.task_id,
+ 'task_status': course_task_log_entry.task_status,
+ 'in_progress': False
+ }
+ return output
+
+ # we need to get information from the task result directly now.
+ result = AsyncResult(task_id)
+
+ output = {
+ 'task_id': result.id,
+ 'task_status': result.state,
+ 'in_progress': True
+ }
+ if result.traceback is not None:
+ output['task_traceback'] = result.traceback
+
+ 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
+
+ # update the entry if necessary:
+ if course_task_log_entry.task_status != result.state:
+ course_task_log_entry.task_status = result.state
+ course_task_log_entry.save()
+
+ return output
+
+
def _reset_problem_attempts_module_state(request, module_to_reset, module_descriptor):
# modify the problem's state
# load the state json and change state
diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py
index 47b22edcb2..67ea0d1ea9 100644
--- a/lms/djangoapps/instructor/views.py
+++ b/lms/djangoapps/instructor/views.py
@@ -29,7 +29,7 @@ from courseware import tasks
from courseware.access import (has_access, get_access_group_name,
course_beta_test_group_name)
from courseware.courses import get_course_with_access
-from courseware.models import StudentModule
+from courseware.models import StudentModule, CourseTaskLog
from django_comment_common.models import (Role,
FORUM_ROLE_ADMINISTRATOR,
FORUM_ROLE_MODERATOR,
@@ -217,10 +217,13 @@ def instructor_dashboard(request, course_id):
elif "Regrade ALL students' problem submissions" in action:
problem_url = request.POST.get('problem_to_regrade', '')
try:
- result = tasks.regrade_problem_for_all_students(request, course_id, problem_url)
+ course_task_log_entry = tasks.regrade_problem_for_all_students(request, course_id, problem_url)
except Exception as e:
- log.error("Encountered exception from regrade: {msg}", msg=e.message())
-
+ log.error("Encountered exception from regrade: {0}", e)
+ # check that a course_task_log entry was created:
+ if course_task_log_entry is None:
+ msg += 'Failed to create a background task for regrading "{0}".'.format(problem_url)
+
elif "Reset student's attempts" in action or "Delete student state for problem" in action:
# get the form data
unique_student_identifier = request.POST.get('unique_student_identifier', '')
@@ -641,6 +644,9 @@ def instructor_dashboard(request, course_id):
if use_offline:
msg += "
Grades from %s" % offline_grades_available(course_id)
+ # generate list of pending background tasks
+ course_tasks = CourseTaskLog.objects.filter(course_id = course_id).exclude(task_status='SUCCESS').exclude(task_status='FAILURE')
+
#----------------------------------------
# context for rendering
@@ -655,7 +661,7 @@ def instructor_dashboard(request, course_id):
'problems': problems, # psychometrics
'plots': plots, # psychometrics
'course_errors': modulestore().get_item_errors(course.location),
-
+ 'course_tasks': course_tasks,
'djangopid': os.getpid(),
'mitx_version': getattr(settings, 'MITX_VERSION_STRING', ''),
'offline_grade_log': offline_grades_available(course_id),
diff --git a/lms/templates/courseware/instructor_dashboard.html b/lms/templates/courseware/instructor_dashboard.html
index f60f591f2f..3b5ea7a0e1 100644
--- a/lms/templates/courseware/instructor_dashboard.html
+++ b/lms/templates/courseware/instructor_dashboard.html
@@ -9,6 +9,110 @@
+
+%if course_tasks is not None:
+
+%endif
%block>
@@ -384,6 +488,43 @@ function goto( mode)
%if msg:
${msg}
%endif +##----------------------------------------------------------------------------- +## Output tasks in progress + +%if course_tasks is not None: +Pending Course Tasks
+| Task Name | +Task Arg | +Student | +Task Id | +Requester | +Submitted | +Last Update | +Task Status | +Task Progress | +
|---|---|---|---|---|---|---|---|---|
| ${course_task.task_name} | +${course_task.task_args} | +${course_task.student} | +${course_task.task_id} |
+ ${course_task.requester} | +${course_task.created} | +${course_task.updated} |
+ ${course_task.task_status} |
+ unknown |
+