Merge branch 'release'
Conflicts: lms/djangoapps/instructor/views/instructor_dashboard.py
This commit is contained in:
@@ -111,7 +111,7 @@ nav.sequence-nav {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus {
|
||||
background-color: #fff;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center 14px;
|
||||
@@ -235,7 +235,7 @@ nav.sequence-nav {
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus {
|
||||
p {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
|
||||
@@ -103,6 +103,7 @@ class @Sequence
|
||||
|
||||
sequence_links = @$('#seq_content a.seqnav')
|
||||
sequence_links.click @goto
|
||||
@$("a.active").blur()
|
||||
|
||||
goto: (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
@@ -95,6 +95,7 @@ if Backbone?
|
||||
@timer = 0
|
||||
@$el.html(@template())
|
||||
|
||||
$(window).bind "load", @updateSidebar
|
||||
$(window).bind "scroll", @updateSidebar
|
||||
$(window).bind "resize", @updateSidebar
|
||||
|
||||
@@ -143,6 +144,18 @@ if Backbone?
|
||||
options.group_id = @group_id
|
||||
|
||||
|
||||
lastThread = @collection.last()?.get('id')
|
||||
if lastThread
|
||||
# Pagination; focus the first thread after what was previously the last thread
|
||||
@once("threads:rendered", ->
|
||||
$(".post-list li:has(a[data-id='#{lastThread}']) + li a").focus()
|
||||
)
|
||||
else
|
||||
# Totally refreshing the list (e.g. from clicking a sort button); focus the first thread
|
||||
@once("threads:rendered", ->
|
||||
$(".post-list a").first()?.focus()
|
||||
)
|
||||
|
||||
@collection.retrieveAnotherPage(@mode, options, {sort_key: @sortBy})
|
||||
|
||||
renderThread: (thread) =>
|
||||
|
||||
@@ -42,8 +42,10 @@ if Backbone?
|
||||
renderVoted: =>
|
||||
if window.user.voted(@model)
|
||||
@$("[data-role=discussion-vote]").addClass("is-cast")
|
||||
@$("[data-role=discussion-vote] span.sr").html("votes (click to remove your vote)")
|
||||
else
|
||||
@$("[data-role=discussion-vote]").removeClass("is-cast")
|
||||
@$("[data-role=discussion-vote] span.sr").html("votes (click to vote)")
|
||||
|
||||
renderFlagged: =>
|
||||
if window.user.id in @model.get("abuse_flaggers") or (DiscussionUtil.isFlagModerator and @model.get("abuse_flaggers").length > 0)
|
||||
@@ -70,7 +72,12 @@ if Backbone?
|
||||
@renderVoted()
|
||||
@renderFlagged()
|
||||
@renderPinned()
|
||||
@$("[data-role=discussion-vote] .votes-count-number").html(@model.get("votes")["up_count"])
|
||||
@$("[data-role=discussion-vote] .votes-count-number").html(@model.get("votes")["up_count"] + '<span class ="sr"></span>')
|
||||
if window.user.voted(@model)
|
||||
@$("[data-role=discussion-vote] .votes-count-number span.sr").html("votes (click to remove your vote)")
|
||||
else
|
||||
@$("[data-role=discussion-vote] .votes-count-number span.sr").html("votes (click to vote)")
|
||||
|
||||
|
||||
convertMath: ->
|
||||
element = @$(".post-body")
|
||||
|
||||
@@ -23,6 +23,7 @@ if Backbone?
|
||||
@delegateEvents()
|
||||
if window.user.voted(@model)
|
||||
@$(".vote-btn").addClass("is-cast")
|
||||
@$(".vote-btn span.sr").html("votes (click to remove your vote)")
|
||||
@renderAttrs()
|
||||
@renderFlagged()
|
||||
@$el.find(".posted-details").timeago()
|
||||
@@ -48,12 +49,14 @@ if Backbone?
|
||||
@$(".vote-btn").toggleClass("is-cast")
|
||||
if @$(".vote-btn").hasClass("is-cast")
|
||||
@vote()
|
||||
@$(".vote-btn span.sr").html("votes (click to remove your vote)")
|
||||
else
|
||||
@unvote()
|
||||
@$(".vote-btn span.sr").html("votes (click to vote)")
|
||||
|
||||
vote: ->
|
||||
url = @model.urlFor("upvote")
|
||||
@$(".votes-count-number").html(parseInt(@$(".votes-count-number").html()) + 1)
|
||||
@$(".votes-count-number").html((parseInt(@$(".votes-count-number").html()) + 1) + '<span class="sr"></span>')
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: @$(".discussion-vote")
|
||||
url: url
|
||||
@@ -64,7 +67,7 @@ if Backbone?
|
||||
|
||||
unvote: ->
|
||||
url = @model.urlFor("unvote")
|
||||
@$(".votes-count-number").html(parseInt(@$(".votes-count-number").html()) - 1)
|
||||
@$(".votes-count-number").html((parseInt(@$(".votes-count-number").html()) - 1)+'<span class="sr"></span>')
|
||||
DiscussionUtil.safeAjax
|
||||
$elem: @$(".discussion-vote")
|
||||
url: url
|
||||
|
||||
@@ -512,7 +512,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
|
||||
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})
|
||||
url += "?student_email={}".format(
|
||||
url += "?unique_student_identifier={}".format(
|
||||
quote(self.students[0].email.encode("utf-8"))
|
||||
)
|
||||
print url
|
||||
@@ -522,6 +522,19 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
|
||||
res_json = json.loads(response.content)
|
||||
self.assertIn('progress_url', res_json)
|
||||
|
||||
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})
|
||||
url += "?unique_student_identifier={}".format(
|
||||
quote(self.students[0].username.encode("utf-8"))
|
||||
)
|
||||
print url
|
||||
response = self.client.get(url)
|
||||
print response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
res_json = json.loads(response.content)
|
||||
self.assertIn('progress_url', res_json)
|
||||
|
||||
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})
|
||||
@@ -579,7 +592,7 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase)
|
||||
url = reverse('reset_student_attempts', kwargs={'course_id': self.course.id})
|
||||
response = self.client.get(url, {
|
||||
'problem_to_reset': self.problem_urlname,
|
||||
'student_email': self.student.email,
|
||||
'unique_student_identifier': self.student.email,
|
||||
})
|
||||
print response.content
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@@ -608,7 +621,7 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase)
|
||||
url = reverse('reset_student_attempts', kwargs={'course_id': self.course.id})
|
||||
response = self.client.get(url, {
|
||||
'problem_to_reset': 'robot-not-a-real-module',
|
||||
'student_email': self.student.email,
|
||||
'unique_student_identifier': self.student.email,
|
||||
})
|
||||
print response.content
|
||||
self.assertEqual(response.status_code, 400)
|
||||
@@ -618,7 +631,7 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase)
|
||||
url = reverse('reset_student_attempts', kwargs={'course_id': self.course.id})
|
||||
response = self.client.get(url, {
|
||||
'problem_to_reset': self.problem_urlname,
|
||||
'student_email': self.student.email,
|
||||
'unique_student_identifier': self.student.email,
|
||||
'delete_module': True,
|
||||
})
|
||||
print response.content
|
||||
@@ -634,11 +647,11 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase)
|
||||
)
|
||||
|
||||
def test_reset_student_attempts_nonsense(self):
|
||||
""" Test failure with both student_email and all_students. """
|
||||
""" Test failure with both unique_student_identifier and all_students. """
|
||||
url = reverse('reset_student_attempts', kwargs={'course_id': self.course.id})
|
||||
response = self.client.get(url, {
|
||||
'problem_to_reset': self.problem_urlname,
|
||||
'student_email': self.student.email,
|
||||
'unique_student_identifier': self.student.email,
|
||||
'all_students': True,
|
||||
})
|
||||
print response.content
|
||||
@@ -650,7 +663,19 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase)
|
||||
url = reverse('rescore_problem', kwargs={'course_id': self.course.id})
|
||||
response = self.client.get(url, {
|
||||
'problem_to_reset': self.problem_urlname,
|
||||
'student_email': self.student.email,
|
||||
'unique_student_identifier': self.student.email,
|
||||
})
|
||||
print response.content
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTrue(act.called)
|
||||
|
||||
@patch.object(instructor_task.api, 'submit_rescore_problem_for_student')
|
||||
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})
|
||||
response = self.client.get(url, {
|
||||
'problem_to_reset': self.problem_urlname,
|
||||
'unique_student_identifier': self.student.username,
|
||||
})
|
||||
print response.content
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@@ -747,7 +772,7 @@ class TestInstructorAPITaskLists(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
url = reverse('list_instructor_tasks', kwargs={'course_id': self.course.id})
|
||||
response = self.client.get(url, {
|
||||
'problem_urlname': self.problem_urlname,
|
||||
'student_email': self.student.email,
|
||||
'unique_student_identifier': self.student.email,
|
||||
})
|
||||
print response.content
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@@ -33,7 +33,7 @@ import instructor_task.api
|
||||
from instructor_task.api_helper import AlreadyRunningError
|
||||
import instructor.enrollment as enrollment
|
||||
from instructor.enrollment import enroll_email, unenroll_email
|
||||
from instructor.views.tools import strip_if_string
|
||||
from instructor.views.tools import strip_if_string, get_student_from_identifier
|
||||
import instructor.access as access
|
||||
import analytics.basic
|
||||
import analytics.distributions
|
||||
@@ -456,20 +456,19 @@ def get_distribution(request, course_id):
|
||||
@common_exceptions_400
|
||||
@require_level('staff')
|
||||
@require_query_params(
|
||||
student_email="email of student for whom to get progress url"
|
||||
unique_student_identifier="email or username of student for whom to get progress url"
|
||||
)
|
||||
def get_student_progress_url(request, course_id):
|
||||
"""
|
||||
Get the progress url of a student.
|
||||
Limited to staff access.
|
||||
|
||||
Takes query paremeter student_email and if the student exists
|
||||
Takes query paremeter unique_student_identifier and if the student exists
|
||||
returns e.g. {
|
||||
'progress_url': '/../...'
|
||||
}
|
||||
"""
|
||||
student_email = strip_if_string(request.GET.get('student_email'))
|
||||
user = User.objects.get(email=student_email)
|
||||
user = get_student_from_identifier(request.GET.get('unique_student_identifier'))
|
||||
|
||||
progress_url = reverse('student_progress', kwargs={'course_id': course_id, 'student_id': user.id})
|
||||
|
||||
@@ -496,7 +495,7 @@ def reset_student_attempts(request, course_id):
|
||||
|
||||
Takes some of the following query paremeters
|
||||
- problem_to_reset is a urlname of a problem
|
||||
- student_email is an email
|
||||
- unique_student_identifier is an email or username
|
||||
- all_students is a boolean
|
||||
requires instructor access
|
||||
mutually exclusive with delete_module
|
||||
@@ -510,14 +509,17 @@ def reset_student_attempts(request, course_id):
|
||||
)
|
||||
|
||||
problem_to_reset = strip_if_string(request.GET.get('problem_to_reset'))
|
||||
student_email = strip_if_string(request.GET.get('student_email'))
|
||||
student_identifier = request.GET.get('unique_student_identifier', None)
|
||||
student = None
|
||||
if student_identifier is not None:
|
||||
student = get_student_from_identifier(student_identifier)
|
||||
all_students = request.GET.get('all_students', False) in ['true', 'True', True]
|
||||
delete_module = request.GET.get('delete_module', False) in ['true', 'True', True]
|
||||
|
||||
# parameter combinations
|
||||
if all_students and student_email:
|
||||
if all_students and student:
|
||||
return HttpResponseBadRequest(
|
||||
"all_students and student_email are mutually exclusive."
|
||||
"all_students and unique_student_identifier are mutually exclusive."
|
||||
)
|
||||
if all_students and delete_module:
|
||||
return HttpResponseBadRequest(
|
||||
@@ -534,15 +536,16 @@ def reset_student_attempts(request, course_id):
|
||||
response_payload = {}
|
||||
response_payload['problem_to_reset'] = problem_to_reset
|
||||
|
||||
if student_email:
|
||||
if student:
|
||||
try:
|
||||
student = User.objects.get(email=student_email)
|
||||
enrollment.reset_student_attempts(course_id, student, module_state_key, delete_module=delete_module)
|
||||
except StudentModule.DoesNotExist:
|
||||
return HttpResponseBadRequest("Module does not exist.")
|
||||
response_payload['student'] = student_identifier
|
||||
elif all_students:
|
||||
instructor_task.api.submit_reset_problem_attempts_for_all_students(request, course_id, module_state_key)
|
||||
response_payload['task'] = 'created'
|
||||
response_payload['student'] = 'All Students'
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
@@ -561,21 +564,25 @@ def rescore_problem(request, course_id):
|
||||
|
||||
Takes either of the following query paremeters
|
||||
- problem_to_reset is a urlname of a problem
|
||||
- student_email is an email
|
||||
- unique_student_identifier is an email or username
|
||||
- all_students is a boolean
|
||||
|
||||
all_students and student_email cannot both be present.
|
||||
all_students and unique_student_identifier cannot both be present.
|
||||
"""
|
||||
problem_to_reset = strip_if_string(request.GET.get('problem_to_reset'))
|
||||
student_email = strip_if_string(request.GET.get('student_email', False))
|
||||
student_identifier = request.GET.get('unique_student_identifier', None)
|
||||
student = None
|
||||
if student_identifier is not None:
|
||||
student = get_student_from_identifier(student_identifier)
|
||||
|
||||
all_students = request.GET.get('all_students') in ['true', 'True', True]
|
||||
|
||||
if not (problem_to_reset and (all_students or student_email)):
|
||||
if not (problem_to_reset and (all_students or student)):
|
||||
return HttpResponseBadRequest("Missing query parameters.")
|
||||
|
||||
if all_students and student_email:
|
||||
if all_students and student:
|
||||
return HttpResponseBadRequest(
|
||||
"Cannot rescore with all_students and student_email."
|
||||
"Cannot rescore with all_students and unique_student_identifier."
|
||||
)
|
||||
|
||||
module_state_key = _msk_from_problem_urlname(course_id, problem_to_reset)
|
||||
@@ -583,9 +590,8 @@ def rescore_problem(request, course_id):
|
||||
response_payload = {}
|
||||
response_payload['problem_to_reset'] = problem_to_reset
|
||||
|
||||
if student_email:
|
||||
response_payload['student_email'] = student_email
|
||||
student = User.objects.get(email=student_email)
|
||||
if student:
|
||||
response_payload['student'] = student_identifier
|
||||
instructor_task.api.submit_rescore_problem_for_student(request, course_id, module_state_key, student)
|
||||
response_payload['task'] = 'created'
|
||||
elif all_students:
|
||||
@@ -608,21 +614,22 @@ def list_instructor_tasks(request, course_id):
|
||||
Takes optional query paremeters.
|
||||
- With no arguments, lists running tasks.
|
||||
- `problem_urlname` lists task history for problem
|
||||
- `problem_urlname` and `student_email` lists task
|
||||
- `problem_urlname` and `unique_student_identifier` lists task
|
||||
history for problem AND student (intersection)
|
||||
"""
|
||||
problem_urlname = strip_if_string(request.GET.get('problem_urlname', False))
|
||||
student_email = strip_if_string(request.GET.get('student_email', False))
|
||||
student = request.GET.get('unique_student_identifier', None)
|
||||
if student is not None:
|
||||
student = get_student_from_identifier(student)
|
||||
|
||||
if student_email and not problem_urlname:
|
||||
if student and not problem_urlname:
|
||||
return HttpResponseBadRequest(
|
||||
"student_email must accompany problem_urlname"
|
||||
"unique_student_identifier must accompany problem_urlname"
|
||||
)
|
||||
|
||||
if problem_urlname:
|
||||
module_state_key = _msk_from_problem_urlname(course_id, problem_urlname)
|
||||
if student_email:
|
||||
student = User.objects.get(email=student_email)
|
||||
if student:
|
||||
tasks = instructor_task.api.get_instructor_task_history(course_id, module_state_key, student)
|
||||
else:
|
||||
tasks = instructor_task.api.get_instructor_task_history(course_id, module_state_key)
|
||||
|
||||
@@ -38,7 +38,7 @@ def instructor_dashboard_2(request, course_id):
|
||||
raise Http404()
|
||||
|
||||
sections = [
|
||||
_section_course_info(course_id),
|
||||
_section_course_info(course_id, access),
|
||||
_section_membership(course_id, access),
|
||||
_section_student_admin(course_id, access),
|
||||
_section_data_download(course_id),
|
||||
@@ -67,18 +67,21 @@ section_display_name will be used to generate link titles in the nav bar.
|
||||
""" # pylint: disable=W0105
|
||||
|
||||
|
||||
def _section_course_info(course_id):
|
||||
def _section_course_info(course_id, access):
|
||||
""" Provide data for the corresponding dashboard section """
|
||||
course = get_course_by_id(course_id, depth=None)
|
||||
|
||||
section_data = {}
|
||||
section_data['section_key'] = 'course_info'
|
||||
section_data['section_display_name'] = _('Course Info')
|
||||
section_data['course_id'] = course_id
|
||||
section_data['course_display_name'] = course.display_name
|
||||
section_data['enrollment_count'] = CourseEnrollment.objects.filter(course_id=course_id, is_active=1).count()
|
||||
section_data['has_started'] = course.has_started()
|
||||
section_data['has_ended'] = course.has_ended()
|
||||
section_data = {
|
||||
'section_key': 'course_info',
|
||||
'section_display_name': _('Course Info'),
|
||||
'course_id': course_id,
|
||||
'access': access,
|
||||
'course_display_name': course.display_name,
|
||||
'enrollment_count': CourseEnrollment.objects.filter(course_id=course_id).count(),
|
||||
'has_started': course.has_started(),
|
||||
'has_ended': course.has_ended(),
|
||||
'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': course_id}),
|
||||
}
|
||||
|
||||
try:
|
||||
advance = lambda memo, (letter, score): "{}: {}, ".format(letter, score) + memo
|
||||
|
||||
@@ -1,7 +1,25 @@
|
||||
"""
|
||||
Tools for the instructor dashboard
|
||||
"""
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
def strip_if_string(value):
|
||||
if isinstance(value, basestring):
|
||||
return value.strip()
|
||||
return value
|
||||
|
||||
|
||||
def get_student_from_identifier(unique_student_identifier):
|
||||
"""
|
||||
Gets a student object using either an email address or username.
|
||||
|
||||
Returns the student object associated with `unique_student_identifier`
|
||||
|
||||
Raises User.DoesNotExist if no user object can be found.
|
||||
"""
|
||||
unique_student_identifier = strip_if_string(unique_student_identifier)
|
||||
if "@" in unique_student_identifier:
|
||||
student = User.objects.get(email=unique_student_identifier)
|
||||
else:
|
||||
student = User.objects.get(username=unique_student_identifier)
|
||||
return student
|
||||
|
||||
@@ -131,9 +131,11 @@ $ ->
|
||||
initialText = $elem.html()
|
||||
$elem.empty()
|
||||
_append = appended_id || ""
|
||||
wmdInputId = "wmd-input#{_append}"
|
||||
$wmdPanel = $("<div>").addClass("wmd-panel")
|
||||
.append($("<div>").attr("id", "wmd-button-bar#{_append}"))
|
||||
.append($("<textarea>").addClass("wmd-input").attr("id", "wmd-input#{_append}").html(initialText))
|
||||
.append($("<label>").addClass("sr").attr("for", wmdInputId).text("Post body"))
|
||||
.append($("<textarea>").addClass("wmd-input").attr("id", wmdInputId).html(initialText))
|
||||
.append($("<div>").attr("id", "wmd-preview#{_append}").addClass("wmd-panel wmd-preview"))
|
||||
$elem.append($wmdPanel)
|
||||
|
||||
|
||||
@@ -80,12 +80,13 @@ class StudentAdmin
|
||||
# gather buttons
|
||||
# some buttons are optional because they can be flipped by the instructor task feature switch
|
||||
# student-specific
|
||||
@$field_student_select = find_and_assert @$section, "input[name='student-select']"
|
||||
@$field_student_select_progress = find_and_assert @$section, "input[name='student-select-progress']"
|
||||
@$field_student_select_grade = find_and_assert @$section, "input[name='student-select-grade']"
|
||||
@$progress_link = find_and_assert @$section, "a.progress-link"
|
||||
@$btn_enroll = find_and_assert @$section, "input[name='enroll']"
|
||||
@$btn_unenroll = find_and_assert @$section, "input[name='unenroll']"
|
||||
@$field_problem_select_single = find_and_assert @$section, "input[name='problem-select-single']"
|
||||
@$btn_reset_attempts_single = find_and_assert @$section, "input[name='reset-attempts-single']"
|
||||
@$btn_enroll = @$section.find "input[name='enroll']"
|
||||
@$btn_unenroll = @$section.find "input[name='unenroll']"
|
||||
@$btn_delete_state_single = @$section.find "input[name='delete-state-single']"
|
||||
@$btn_rescore_problem_single = @$section.find "input[name='rescore-problem-single']"
|
||||
@$btn_task_history_single = @$section.find "input[name='task-history-single']"
|
||||
@@ -100,7 +101,8 @@ class StudentAdmin
|
||||
@$table_running_tasks = @$section.find ".running-tasks-table"
|
||||
|
||||
# response areas
|
||||
@$request_response_error_single = find_and_assert @$section, ".student-specific-container .request-response-error"
|
||||
@$request_response_error_progress = find_and_assert @$section, ".student-specific-container .request-response-error"
|
||||
@$request_response_error_grade = find_and_assert @$section, ".student-grade-container .request-response-error"
|
||||
@$request_response_error_all = @$section.find ".course-specific-container .request-response-error"
|
||||
|
||||
# start polling for task list
|
||||
@@ -117,21 +119,23 @@ class StudentAdmin
|
||||
# go to student progress page
|
||||
@$progress_link.click (e) =>
|
||||
e.preventDefault()
|
||||
email = @$field_student_select.val()
|
||||
unique_student_identifier = @$field_student_select_progress.val()
|
||||
if not unique_student_identifier
|
||||
return @$request_response_error_progress.text "Please enter a student email address or username."
|
||||
|
||||
$.ajax
|
||||
dataType: 'json'
|
||||
url: @$progress_link.data 'endpoint'
|
||||
data: student_email: email
|
||||
data: unique_student_identifier: unique_student_identifier
|
||||
success: @clear_errors_then (data) ->
|
||||
window.location = data.progress_url
|
||||
error: std_ajax_err => @$request_response_error_single.text "Error getting student progress url for '#{email}'."
|
||||
error: std_ajax_err => @$request_response_error_progress.text "Error getting student progress url for '#{unique_student_identifier}'."
|
||||
|
||||
# enroll student
|
||||
@$btn_enroll.click =>
|
||||
send_data =
|
||||
action: 'enroll'
|
||||
emails: @$field_student_select.val()
|
||||
emails: @$field_student_select_progress.val()
|
||||
auto_enroll: false
|
||||
|
||||
$.ajax
|
||||
@@ -139,74 +143,95 @@ class StudentAdmin
|
||||
url: @$btn_enroll.data 'endpoint'
|
||||
data: send_data
|
||||
success: @clear_errors_then -> console.log "student #{send_data.emails} enrolled"
|
||||
error: std_ajax_err => @$request_response_error_single.text "Error enrolling student '#{send_data.emails}'."
|
||||
error: std_ajax_err => @$request_response_error_progress.text "Error enrolling student '#{send_data.emails}'."
|
||||
|
||||
# unenroll student
|
||||
@$btn_unenroll.click =>
|
||||
send_data =
|
||||
action: 'unenroll'
|
||||
emails: @$field_student_select.val()
|
||||
emails: @$field_student_select_progress.val()
|
||||
|
||||
$.ajax
|
||||
dataType: 'json'
|
||||
url: @$btn_unenroll.data 'endpoint'
|
||||
data: send_data
|
||||
success: @clear_errors_then -> console.log "student #{send_data.emails} unenrolled"
|
||||
error: std_ajax_err => @$request_response_error_single.text "Error unenrolling student '#{send_data.emails}'."
|
||||
error: std_ajax_err => @$request_response_error_progress.text "Error unenrolling student '#{send_data.emails}'."
|
||||
|
||||
# reset attempts for student on problem
|
||||
@$btn_reset_attempts_single.click =>
|
||||
unique_student_identifier = @$field_student_select_grade.val()
|
||||
problem_to_reset = @$field_problem_select_single.val()
|
||||
if not unique_student_identifier
|
||||
return @$request_response_error_grade.text "Please enter a student email address or username."
|
||||
if not problem_to_reset
|
||||
return @$request_response_error_grade.text "Please enter a problem urlname."
|
||||
send_data =
|
||||
student_email: @$field_student_select.val()
|
||||
problem_to_reset: @$field_problem_select_single.val()
|
||||
unique_student_identifier: unique_student_identifier
|
||||
problem_to_reset: problem_to_reset
|
||||
delete_module: false
|
||||
|
||||
$.ajax
|
||||
dataType: 'json'
|
||||
url: @$btn_reset_attempts_single.data 'endpoint'
|
||||
data: send_data
|
||||
success: @clear_errors_then -> console.log 'problem attempts reset'
|
||||
error: std_ajax_err => @$request_response_error_single.text "Error resetting problem attempts."
|
||||
success: @clear_errors_then -> alert "Success! Problem attempts reset for problem '#{problem_to_reset}' and student '#{unique_student_identifier}'."
|
||||
error: std_ajax_err => @$request_response_error_grade.text "Error resetting problem attempts for problem '#{problem_to_reset}' and student '#{unique_student_identifier}'."
|
||||
|
||||
# delete state for student on problem
|
||||
@$btn_delete_state_single.click => confirm_then
|
||||
msg: "Delete student '#{@$field_student_select.val()}'s state on problem '#{@$field_problem_select_single.val()}'?"
|
||||
ok: =>
|
||||
@$btn_delete_state_single.click =>
|
||||
unique_student_identifier = @$field_student_select_grade.val()
|
||||
problem_to_reset = @$field_problem_select_single.val()
|
||||
if not unique_student_identifier
|
||||
return @$request_response_error_grade.text "Please enter a student email address or username."
|
||||
if not problem_to_reset
|
||||
return @$request_response_error_grade.text "Please enter a problem urlname."
|
||||
|
||||
if window.confirm "Delete student '#{unique_student_identifier}'s state on problem '#{problem_to_reset}'?"
|
||||
send_data =
|
||||
student_email: @$field_student_select.val()
|
||||
problem_to_reset: @$field_problem_select_single.val()
|
||||
unique_student_identifier: unique_student_identifier
|
||||
problem_to_reset: problem_to_reset
|
||||
delete_module: true
|
||||
|
||||
$.ajax
|
||||
dataType: 'json'
|
||||
url: @$btn_delete_state_single.data 'endpoint'
|
||||
data: send_data
|
||||
success: @clear_errors_then -> console.log 'module state deleted'
|
||||
error: std_ajax_err => @$request_response_error_single.text "Error deleting problem state."
|
||||
success: @clear_errors_then -> alert 'Module state successfully deleted.'
|
||||
error: std_ajax_err => @$request_response_error_grade.text "Error deleting problem state."
|
||||
else
|
||||
@clear_errors()
|
||||
|
||||
# start task to rescore problem for student
|
||||
@$btn_rescore_problem_single.click =>
|
||||
unique_student_identifier = @$field_student_select_grade.val()
|
||||
problem_to_reset = @$field_problem_select_single.val()
|
||||
if not unique_student_identifier
|
||||
return @$request_response_error_grade.text "Please enter a student email address or username."
|
||||
if not problem_to_reset
|
||||
return @$request_response_error_grade.text "Please enter a problem urlname."
|
||||
send_data =
|
||||
student_email: @$field_student_select.val()
|
||||
problem_to_reset: @$field_problem_select_single.val()
|
||||
unique_student_identifier: unique_student_identifier
|
||||
problem_to_reset: problem_to_reset
|
||||
|
||||
$.ajax
|
||||
dataType: 'json'
|
||||
url: @$btn_rescore_problem_single.data 'endpoint'
|
||||
data: send_data
|
||||
success: @clear_errors_then -> console.log 'started rescore problem task'
|
||||
error: std_ajax_err => @$request_response_error_single.text "Error starting a task to rescore student's problem."
|
||||
success: @clear_errors_then -> alert "Started rescore problem task for problem '#{problem_to_reset}' and student '#{unique_student_identifier}'. Click the 'Show Background Task History for Student' button to see the status of the task."
|
||||
error: std_ajax_err => @$request_response_error_grade.text "Error starting a task to rescore problem '#{problem_to_reset}' for student '#{unique_student_identifier}'."
|
||||
|
||||
# list task history for student+problem
|
||||
@$btn_task_history_single.click =>
|
||||
unique_student_identifier = @$field_student_select_grade.val()
|
||||
problem_to_reset = @$field_problem_select_single.val()
|
||||
if not unique_student_identifier
|
||||
return @$request_response_error_grade.text "Please enter a student email address or username."
|
||||
if not problem_to_reset
|
||||
return @$request_response_error_grade.text "Please enter a problem urlname."
|
||||
send_data =
|
||||
student_email: @$field_student_select.val()
|
||||
problem_urlname: @$field_problem_select_single.val()
|
||||
|
||||
if not send_data.student_email
|
||||
return @$request_response_error_single.text "Enter a student email."
|
||||
if not send_data.problem_urlname
|
||||
return @$request_response_error_single.text "Enter a problem urlname."
|
||||
unique_student_identifier: unique_student_identifier
|
||||
problem_urlname: problem_to_reset
|
||||
|
||||
$.ajax
|
||||
dataType: 'json'
|
||||
@@ -214,37 +239,45 @@ class StudentAdmin
|
||||
data: send_data
|
||||
success: @clear_errors_then (data) =>
|
||||
create_task_list_table @$table_task_history_single, data.tasks
|
||||
error: std_ajax_err => @$request_response_error_single.text "Error getting task history for student+problem"
|
||||
error: std_ajax_err => @$request_response_error_grade.text "Error getting task history for student '#{unique_student_identifier}' and problem '#{problem_to_reset}'."
|
||||
|
||||
# start task to reset attempts on problem for all students
|
||||
@$btn_reset_attempts_all.click => confirm_then
|
||||
msg: "Reset attempts for all students on problem '#{@$field_problem_select_all.val()}'?"
|
||||
ok: =>
|
||||
@$btn_reset_attempts_all.click =>
|
||||
problem_to_reset = @$field_problem_select_all.val()
|
||||
if not problem_to_reset
|
||||
return @$request_response_error_all.text "Please enter a problem urlname."
|
||||
if window.confirm "Reset attempts for all students on problem '#{@$field_problem_select_all.val()}'?"
|
||||
send_data =
|
||||
all_students: true
|
||||
problem_to_reset: @$field_problem_select_all.val()
|
||||
problem_to_reset: problem_to_reset
|
||||
|
||||
$.ajax
|
||||
dataType: 'json'
|
||||
url: @$btn_reset_attempts_all.data 'endpoint'
|
||||
data: send_data
|
||||
success: @clear_errors_then -> console.log 'started reset attempts task'
|
||||
success: @clear_errors_then -> alert "Successfully started task to reset attempts for problem '#{problem_to_reset}'. Click the 'Show Background Task History for Problem' button to see the status of the task."
|
||||
error: std_ajax_err => @$request_response_error_all.text "Error starting a task to reset attempts for all students on this problem."
|
||||
else
|
||||
@clear_errors()
|
||||
|
||||
# start task to rescore problem for all students
|
||||
@$btn_rescore_problem_all.click => confirm_then
|
||||
msg: "Rescore problem '#{@$field_problem_select_all.val()}' for all students?"
|
||||
ok: =>
|
||||
@$btn_rescore_problem_all.click =>
|
||||
problem_to_reset = @$field_problem_select_all.val()
|
||||
if not problem_to_reset
|
||||
return @$request_response_error_all.text "Please enter a problem urlname."
|
||||
if window.confirm "Rescore problem '#{@$field_problem_select_all.val()}' for all students?"
|
||||
send_data =
|
||||
all_students: true
|
||||
problem_to_reset: @$field_problem_select_all.val()
|
||||
problem_to_reset: problem_to_reset
|
||||
|
||||
$.ajax
|
||||
dataType: 'json'
|
||||
url: @$btn_rescore_problem_all.data 'endpoint'
|
||||
data: send_data
|
||||
success: @clear_errors_then -> console.log 'started rescore problem task'
|
||||
success: @clear_errors_then -> alert "Successfully started task to rescore problem '#{problem_to_reset}' for all students. Click the 'Show Background Task History for Problem' button to see the status of the task."
|
||||
error: std_ajax_err => @$request_response_error_all.text "Error starting a task to rescore this problem for all students."
|
||||
else
|
||||
@clear_errors()
|
||||
|
||||
# list task history for problem
|
||||
@$btn_task_history_all.click =>
|
||||
@@ -252,7 +285,7 @@ class StudentAdmin
|
||||
problem_urlname: @$field_problem_select_all.val()
|
||||
|
||||
if not send_data.problem_urlname
|
||||
return @$request_response_error_all.text "Enter a problem urlname."
|
||||
return @$request_response_error_all.text "Please enter a problem urlname."
|
||||
|
||||
$.ajax
|
||||
dataType: 'json'
|
||||
@@ -272,11 +305,18 @@ class StudentAdmin
|
||||
|
||||
# wraps a function, but first clear the error displays
|
||||
clear_errors_then: (cb) ->
|
||||
@$request_response_error_single.empty()
|
||||
@$request_response_error_progress.empty()
|
||||
@$request_response_error_grade.empty()
|
||||
@$request_response_error_all.empty()
|
||||
->
|
||||
cb?.apply this, arguments
|
||||
|
||||
|
||||
clear_errors: ->
|
||||
@$request_response_error_progress.empty()
|
||||
@$request_response_error_grade.empty()
|
||||
@$request_response_error_all.empty()
|
||||
|
||||
# handler for when the section title is clicked.
|
||||
onClickTitle: -> @task_poller?.start()
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
text-shadow: 0 1px 0 rgba(0, 0, 0, .3);
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4) inset, 0 1px 1px rgba(0, 0, 0, .15);
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus {
|
||||
border-color: #297095;
|
||||
@include linear-gradient(top, #4fbbe4, #2090d0);
|
||||
}
|
||||
@@ -32,7 +32,7 @@
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4) inset, 0 1px 1px rgba(0, 0, 0, .15);
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus {
|
||||
@include linear-gradient(top, $white, #ddd);
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.6);
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4) inset, 0 1px 1px rgba(0, 0, 0, .15);
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus {
|
||||
background: -webkit-linear-gradient(top, #888, #666);
|
||||
}
|
||||
}
|
||||
@@ -217,8 +217,7 @@ body.discussion {
|
||||
color: #eee;
|
||||
@include transition(none);
|
||||
|
||||
&:hover,
|
||||
&.focused {
|
||||
&:hover, &:focus {
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
@@ -305,7 +304,7 @@ body.discussion {
|
||||
padding-bottom: 2px;
|
||||
height: 37px;
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus {
|
||||
border-color: #222;
|
||||
}
|
||||
}
|
||||
@@ -436,7 +435,7 @@ body.discussion {
|
||||
height: 37px;
|
||||
border-color: #333;
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus {
|
||||
border-color: #222;
|
||||
}
|
||||
}
|
||||
@@ -714,7 +713,7 @@ body.discussion {
|
||||
height: 100%;
|
||||
background-color: #dedede;
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus {
|
||||
background-color: $white;
|
||||
}
|
||||
}
|
||||
@@ -881,8 +880,7 @@ body.discussion {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.focused {
|
||||
&:hover, &:focus {
|
||||
background-color: #636363;
|
||||
}
|
||||
|
||||
@@ -1015,7 +1013,7 @@ body.discussion {
|
||||
color: #333;
|
||||
line-height: 17px;
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus {
|
||||
@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, .2));
|
||||
color: #333;
|
||||
}
|
||||
@@ -1067,7 +1065,7 @@ body.discussion {
|
||||
line-height: 33px;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus {
|
||||
background-image: none;
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
@@ -1086,7 +1084,7 @@ body.discussion {
|
||||
background-color: $white;
|
||||
@include clearfix;
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus {
|
||||
@include linear-gradient(top, rgba(255, 255, 255, .7), rgba(255, 255, 255, 0));
|
||||
background-color: #eee;
|
||||
}
|
||||
@@ -2225,7 +2223,7 @@ body.discussion {
|
||||
padding-bottom: 2px;
|
||||
border-color: #333;
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus {
|
||||
border-color: #222;
|
||||
}
|
||||
}
|
||||
@@ -2522,7 +2520,7 @@ body.discussion {
|
||||
margin-top: $baseline/2;
|
||||
padding-bottom: 2px;
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus {
|
||||
border-color: #222;
|
||||
}
|
||||
}
|
||||
@@ -2596,7 +2594,7 @@ body.discussion {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus {
|
||||
@include transition(opacity .2s linear 0s);
|
||||
opacity: 1.0;
|
||||
}
|
||||
@@ -2659,7 +2657,7 @@ display:none;
|
||||
cursor:pointer;
|
||||
opacity: 0.8;
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus {
|
||||
@include transition(opacity .2s linear 0s);
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
% num_results = len(results)
|
||||
<% num_results = len(results) %>
|
||||
% for (i,result) in enumerate(results):
|
||||
% if 'task_name' in result and 'result' in result:
|
||||
<div class="combined-rubric-container"
|
||||
|
||||
@@ -240,7 +240,7 @@ function goto( mode)
|
||||
<hr width="40%" style="align:left">
|
||||
%endif
|
||||
|
||||
<H2>${_("Student-specific grade inspection and adjustment")}</h2>
|
||||
<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">
|
||||
|
||||
@@ -38,8 +38,21 @@
|
||||
## ${ section_data['offline_grades'] }
|
||||
## </div>
|
||||
|
||||
%if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS') and section_data['access']['instructor']:
|
||||
<div class="running-tasks-container action-type-container">
|
||||
<hr>
|
||||
<h2> ${_("Pending Instructor Tasks")} </h2>
|
||||
<p>${_("The status for any active tasks appears in a table below.")} </p>
|
||||
|
||||
<div class="running-tasks-table" data-endpoint="${ section_data['list_instructor_tasks_url'] }"></div>
|
||||
</div>
|
||||
|
||||
%endif
|
||||
|
||||
%if len(section_data['course_errors']):
|
||||
<div class="course-errors-wrapper">
|
||||
<hr>
|
||||
<p>
|
||||
<div class="toggle-wrapper">
|
||||
<h2 class="title">${_("Course Warnings")}:</h2>
|
||||
<div class="triangle"></div>
|
||||
@@ -52,5 +65,10 @@
|
||||
</div>
|
||||
%endfor
|
||||
</div>
|
||||
<p>
|
||||
</div>
|
||||
<br>
|
||||
%endif
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,25 +2,49 @@
|
||||
<%page args="section_data"/>
|
||||
|
||||
<div class="student-specific-container action-type-container">
|
||||
<H2>${_("Student-specific grade adjustment")}</h2>
|
||||
<h2>${_("Student-specific grade inspection")}</h2>
|
||||
<div class="request-response-error"></div>
|
||||
|
||||
<input type="text" name="student-select" placeholder="${_("Student Email")}">
|
||||
<p>
|
||||
<!-- Doesn't work for username but this MUST work -->
|
||||
${_("Specify the {platform_name} email address or username of a student here:").format(platform_name=settings.PLATFORM_NAME)}
|
||||
<input type="text" name="student-select-progress" placeholder="${_("Student Email or Username")}">
|
||||
</p>
|
||||
<br>
|
||||
|
||||
<div class="progress-link-wrapper">
|
||||
<a href="" class="progress-link" data-endpoint="${ section_data['get_student_progress_url_url'] }"> ${_("Student Progress Page")} </a>
|
||||
</div>
|
||||
<p>
|
||||
${_("Click this link to view the student's progress page:")}
|
||||
|
||||
<a href="" class="progress-link" data-endpoint="${ section_data['get_student_progress_url_url'] }"> ${_("Student Progress Page")} </a>
|
||||
</p>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<!-- These buttons don't appear to be working
|
||||
<p>
|
||||
${_("Click to enroll or unenroll this student from the course:")}
|
||||
<input type="button" name="enroll" value="${_("Enroll")}" data-endpoint="${ section_data['enrollment_url'] }">
|
||||
<input type="button" name="unenroll" value="${_("Unenroll")}" data-endpoint="${ section_data['enrollment_url'] }">
|
||||
## <select class="problems">
|
||||
## <option>Getting problems...</option>
|
||||
## </select>
|
||||
</p>
|
||||
-->
|
||||
|
||||
<p> ${_('Specify a particular problem in the course here by its url:')} </p>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div class="student-grade-container action-type-container">
|
||||
<h2>${_("Student-specific grade adjustment")}</h2>
|
||||
<div class="request-response-error"></div>
|
||||
<p>
|
||||
<!-- Doesn't work for username but this MUST work -->
|
||||
${_("Specify the {platform_name} email address or username of a student here:").format(platform_name=settings.PLATFORM_NAME)}
|
||||
<input type="text" name="student-select-grade" placeholder="${_("Student Email or Username")}">
|
||||
</p>
|
||||
<br>
|
||||
|
||||
<p> ${_('Specify a particular problem in the course here by its url:')}
|
||||
<input type="text" name="problem-select-single" placeholder="${_("Problem urlname")}">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
${_('You may use just the "urlname" if a problem, or "modulename/urlname" if not. (For example, if the location is {location1}, then just provide the {urlname1}. If the location is {location2}, then provide {urlname2}.)').format(
|
||||
location1="<tt>i4x://university/course/problem/problemname</tt>",
|
||||
@@ -29,20 +53,31 @@
|
||||
urlname2="<tt>notaproblem/someothername</tt>")
|
||||
}
|
||||
</p>
|
||||
<input type="button" name="reset-attempts-single" value="${_("Reset Student Attempts")}" data-endpoint="${ section_data['reset_student_attempts_url'] }">
|
||||
|
||||
%if section_data['access']['instructor']:
|
||||
<p> ${_('You may also delete the entire state of a student for the specified module:')} </p>
|
||||
<input type="button" class="molly-guard" name="delete-state-single" value="${_("Delete Student State for Module")}" data-endpoint="${ section_data['reset_student_attempts_url'] }">
|
||||
%endif
|
||||
<p>
|
||||
${_("Next, select an action to perform for the given user and problem:")}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<!-- Doesn't give any type of notification upon success -->
|
||||
<input type="button" name="reset-attempts-single" value="${_("Reset Student Attempts")}" data-endpoint="${ section_data['reset_student_attempts_url'] }">
|
||||
|
||||
%if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS') and section_data['access']['instructor']:
|
||||
<input type="button" name="rescore-problem-single" value="${_("Rescore Student Submission")}" data-endpoint="${ section_data['rescore_problem_url'] }">
|
||||
%endif
|
||||
</p>
|
||||
|
||||
<p>
|
||||
%if section_data['access']['instructor']:
|
||||
<p> ${_('You may also delete the entire state of a student for the specified problem:')} </p>
|
||||
<input type="button" class="molly-guard" name="delete-state-single" value="${_("Delete Student State for Problem")}" data-endpoint="${ section_data['reset_student_attempts_url'] }">
|
||||
%endif
|
||||
</p>
|
||||
|
||||
|
||||
%if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS') and section_data['access']['instructor']:
|
||||
<p>
|
||||
${_("Rescoring runs in the background, and status for active tasks will appear in a table below. "
|
||||
${_("Rescoring runs in the background, and status for active tasks will appear in a table on the Course Info tab. "
|
||||
"To see status for all tasks submitted for this problem and student, click on this button:")}
|
||||
</p>
|
||||
|
||||
@@ -76,18 +111,11 @@
|
||||
</p>
|
||||
<p>
|
||||
<p>
|
||||
${_("These actions run in the background, and status for active tasks will appear in a table below. "
|
||||
${_("These actions run in the background, and status for active tasks will appear in a table on the Course Info tab. "
|
||||
"To see status for all tasks submitted for this problem, click on this button")}:
|
||||
</p>
|
||||
<input type="button" name="task-history-all" value="${_("Show Background Task History for Problem")}" data-endpoint="${ section_data['list_instructor_tasks_url'] }">
|
||||
<div class="task-history-all-table"></div>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="running-tasks-container action-type-container">
|
||||
<hr>
|
||||
<h2> ${_("Pending Instructor Tasks")} </h2>
|
||||
<div class="running-tasks-table" data-endpoint="${ section_data['list_instructor_tasks_url'] }"></div>
|
||||
</div>
|
||||
%endif
|
||||
|
||||
Reference in New Issue
Block a user