Commit 81a2ac41 by marco

Merge branch 'release'

Conflicts:
	lms/djangoapps/instructor/views/instructor_dashboard.py
parents bb60c404 c850be4a
...@@ -111,7 +111,7 @@ nav.sequence-nav { ...@@ -111,7 +111,7 @@ nav.sequence-nav {
outline: 0; outline: 0;
} }
&:hover { &:hover, &:focus {
background-color: #fff; background-color: #fff;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center 14px; background-position: center 14px;
...@@ -235,7 +235,7 @@ nav.sequence-nav { ...@@ -235,7 +235,7 @@ nav.sequence-nav {
} }
} }
&:hover { &:hover, &:focus {
p { p {
display: block; display: block;
margin-top: 4px; margin-top: 4px;
......
...@@ -103,6 +103,7 @@ class @Sequence ...@@ -103,6 +103,7 @@ class @Sequence
sequence_links = @$('#seq_content a.seqnav') sequence_links = @$('#seq_content a.seqnav')
sequence_links.click @goto sequence_links.click @goto
@$("a.active").blur()
goto: (event) => goto: (event) =>
event.preventDefault() event.preventDefault()
......
...@@ -95,6 +95,7 @@ if Backbone? ...@@ -95,6 +95,7 @@ if Backbone?
@timer = 0 @timer = 0
@$el.html(@template()) @$el.html(@template())
$(window).bind "load", @updateSidebar
$(window).bind "scroll", @updateSidebar $(window).bind "scroll", @updateSidebar
$(window).bind "resize", @updateSidebar $(window).bind "resize", @updateSidebar
...@@ -143,6 +144,18 @@ if Backbone? ...@@ -143,6 +144,18 @@ if Backbone?
options.group_id = @group_id 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}) @collection.retrieveAnotherPage(@mode, options, {sort_key: @sortBy})
renderThread: (thread) => renderThread: (thread) =>
......
...@@ -42,8 +42,10 @@ if Backbone? ...@@ -42,8 +42,10 @@ if Backbone?
renderVoted: => renderVoted: =>
if window.user.voted(@model) if window.user.voted(@model)
@$("[data-role=discussion-vote]").addClass("is-cast") @$("[data-role=discussion-vote]").addClass("is-cast")
@$("[data-role=discussion-vote] span.sr").html("votes (click to remove your vote)")
else else
@$("[data-role=discussion-vote]").removeClass("is-cast") @$("[data-role=discussion-vote]").removeClass("is-cast")
@$("[data-role=discussion-vote] span.sr").html("votes (click to vote)")
renderFlagged: => renderFlagged: =>
if window.user.id in @model.get("abuse_flaggers") or (DiscussionUtil.isFlagModerator and @model.get("abuse_flaggers").length > 0) 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? ...@@ -70,7 +72,12 @@ if Backbone?
@renderVoted() @renderVoted()
@renderFlagged() @renderFlagged()
@renderPinned() @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: -> convertMath: ->
element = @$(".post-body") element = @$(".post-body")
......
...@@ -23,6 +23,7 @@ if Backbone? ...@@ -23,6 +23,7 @@ if Backbone?
@delegateEvents() @delegateEvents()
if window.user.voted(@model) if window.user.voted(@model)
@$(".vote-btn").addClass("is-cast") @$(".vote-btn").addClass("is-cast")
@$(".vote-btn span.sr").html("votes (click to remove your vote)")
@renderAttrs() @renderAttrs()
@renderFlagged() @renderFlagged()
@$el.find(".posted-details").timeago() @$el.find(".posted-details").timeago()
...@@ -48,12 +49,14 @@ if Backbone? ...@@ -48,12 +49,14 @@ if Backbone?
@$(".vote-btn").toggleClass("is-cast") @$(".vote-btn").toggleClass("is-cast")
if @$(".vote-btn").hasClass("is-cast") if @$(".vote-btn").hasClass("is-cast")
@vote() @vote()
@$(".vote-btn span.sr").html("votes (click to remove your vote)")
else else
@unvote() @unvote()
@$(".vote-btn span.sr").html("votes (click to vote)")
vote: -> vote: ->
url = @model.urlFor("upvote") 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 DiscussionUtil.safeAjax
$elem: @$(".discussion-vote") $elem: @$(".discussion-vote")
url: url url: url
...@@ -64,7 +67,7 @@ if Backbone? ...@@ -64,7 +67,7 @@ if Backbone?
unvote: -> unvote: ->
url = @model.urlFor("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 DiscussionUtil.safeAjax
$elem: @$(".discussion-vote") $elem: @$(".discussion-vote")
url: url url: url
......
...@@ -512,7 +512,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa ...@@ -512,7 +512,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
def test_get_student_progress_url(self): def test_get_student_progress_url(self):
""" Test that progress_url is in the successful response. """ """ Test that progress_url is in the successful response. """
url = reverse('get_student_progress_url', kwargs={'course_id': self.course.id}) 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")) quote(self.students[0].email.encode("utf-8"))
) )
print url print url
...@@ -522,6 +522,19 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa ...@@ -522,6 +522,19 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
res_json = json.loads(response.content) res_json = json.loads(response.content)
self.assertIn('progress_url', res_json) 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): def test_get_student_progress_url_noparams(self):
""" Test that the endpoint 404's without the required query params. """ """ Test that the endpoint 404's without the required query params. """
url = reverse('get_student_progress_url', kwargs={'course_id': self.course.id}) url = reverse('get_student_progress_url', kwargs={'course_id': self.course.id})
...@@ -579,7 +592,7 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase) ...@@ -579,7 +592,7 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase)
url = reverse('reset_student_attempts', kwargs={'course_id': self.course.id}) url = reverse('reset_student_attempts', kwargs={'course_id': self.course.id})
response = self.client.get(url, { response = self.client.get(url, {
'problem_to_reset': self.problem_urlname, 'problem_to_reset': self.problem_urlname,
'student_email': self.student.email, 'unique_student_identifier': self.student.email,
}) })
print response.content print response.content
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
...@@ -608,7 +621,7 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase) ...@@ -608,7 +621,7 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase)
url = reverse('reset_student_attempts', kwargs={'course_id': self.course.id}) url = reverse('reset_student_attempts', kwargs={'course_id': self.course.id})
response = self.client.get(url, { response = self.client.get(url, {
'problem_to_reset': 'robot-not-a-real-module', 'problem_to_reset': 'robot-not-a-real-module',
'student_email': self.student.email, 'unique_student_identifier': self.student.email,
}) })
print response.content print response.content
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
...@@ -618,7 +631,7 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase) ...@@ -618,7 +631,7 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase)
url = reverse('reset_student_attempts', kwargs={'course_id': self.course.id}) url = reverse('reset_student_attempts', kwargs={'course_id': self.course.id})
response = self.client.get(url, { response = self.client.get(url, {
'problem_to_reset': self.problem_urlname, 'problem_to_reset': self.problem_urlname,
'student_email': self.student.email, 'unique_student_identifier': self.student.email,
'delete_module': True, 'delete_module': True,
}) })
print response.content print response.content
...@@ -634,11 +647,11 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase) ...@@ -634,11 +647,11 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase)
) )
def test_reset_student_attempts_nonsense(self): 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}) url = reverse('reset_student_attempts', kwargs={'course_id': self.course.id})
response = self.client.get(url, { response = self.client.get(url, {
'problem_to_reset': self.problem_urlname, 'problem_to_reset': self.problem_urlname,
'student_email': self.student.email, 'unique_student_identifier': self.student.email,
'all_students': True, 'all_students': True,
}) })
print response.content print response.content
...@@ -650,7 +663,19 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase) ...@@ -650,7 +663,19 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase)
url = reverse('rescore_problem', kwargs={'course_id': self.course.id}) url = reverse('rescore_problem', kwargs={'course_id': self.course.id})
response = self.client.get(url, { response = self.client.get(url, {
'problem_to_reset': self.problem_urlname, '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 print response.content
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
...@@ -747,7 +772,7 @@ class TestInstructorAPITaskLists(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -747,7 +772,7 @@ class TestInstructorAPITaskLists(ModuleStoreTestCase, LoginEnrollmentTestCase):
url = reverse('list_instructor_tasks', kwargs={'course_id': self.course.id}) url = reverse('list_instructor_tasks', kwargs={'course_id': self.course.id})
response = self.client.get(url, { response = self.client.get(url, {
'problem_urlname': self.problem_urlname, 'problem_urlname': self.problem_urlname,
'student_email': self.student.email, 'unique_student_identifier': self.student.email,
}) })
print response.content print response.content
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
......
...@@ -33,7 +33,7 @@ import instructor_task.api ...@@ -33,7 +33,7 @@ import instructor_task.api
from instructor_task.api_helper import AlreadyRunningError from instructor_task.api_helper import AlreadyRunningError
import instructor.enrollment as enrollment import instructor.enrollment as enrollment
from instructor.enrollment import enroll_email, unenroll_email 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 instructor.access as access
import analytics.basic import analytics.basic
import analytics.distributions import analytics.distributions
...@@ -456,20 +456,19 @@ def get_distribution(request, course_id): ...@@ -456,20 +456,19 @@ def get_distribution(request, course_id):
@common_exceptions_400 @common_exceptions_400
@require_level('staff') @require_level('staff')
@require_query_params( @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): def get_student_progress_url(request, course_id):
""" """
Get the progress url of a student. Get the progress url of a student.
Limited to staff access. 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. { returns e.g. {
'progress_url': '/../...' 'progress_url': '/../...'
} }
""" """
student_email = strip_if_string(request.GET.get('student_email')) user = get_student_from_identifier(request.GET.get('unique_student_identifier'))
user = User.objects.get(email=student_email)
progress_url = reverse('student_progress', kwargs={'course_id': course_id, 'student_id': user.id}) 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): ...@@ -496,7 +495,7 @@ def reset_student_attempts(request, course_id):
Takes some of the following query paremeters Takes some of the following query paremeters
- problem_to_reset is a urlname of a problem - 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 is a boolean
requires instructor access requires instructor access
mutually exclusive with delete_module mutually exclusive with delete_module
...@@ -510,14 +509,17 @@ def reset_student_attempts(request, course_id): ...@@ -510,14 +509,17 @@ def reset_student_attempts(request, course_id):
) )
problem_to_reset = strip_if_string(request.GET.get('problem_to_reset')) 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] all_students = request.GET.get('all_students', False) in ['true', 'True', True]
delete_module = request.GET.get('delete_module', False) in ['true', 'True', True] delete_module = request.GET.get('delete_module', False) in ['true', 'True', True]
# parameter combinations # parameter combinations
if all_students and student_email: if all_students and student:
return HttpResponseBadRequest( 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: if all_students and delete_module:
return HttpResponseBadRequest( return HttpResponseBadRequest(
...@@ -534,15 +536,16 @@ def reset_student_attempts(request, course_id): ...@@ -534,15 +536,16 @@ def reset_student_attempts(request, course_id):
response_payload = {} response_payload = {}
response_payload['problem_to_reset'] = problem_to_reset response_payload['problem_to_reset'] = problem_to_reset
if student_email: if student:
try: try:
student = User.objects.get(email=student_email)
enrollment.reset_student_attempts(course_id, student, module_state_key, delete_module=delete_module) enrollment.reset_student_attempts(course_id, student, module_state_key, delete_module=delete_module)
except StudentModule.DoesNotExist: except StudentModule.DoesNotExist:
return HttpResponseBadRequest("Module does not exist.") return HttpResponseBadRequest("Module does not exist.")
response_payload['student'] = student_identifier
elif all_students: elif all_students:
instructor_task.api.submit_reset_problem_attempts_for_all_students(request, course_id, module_state_key) instructor_task.api.submit_reset_problem_attempts_for_all_students(request, course_id, module_state_key)
response_payload['task'] = 'created' response_payload['task'] = 'created'
response_payload['student'] = 'All Students'
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
...@@ -561,21 +564,25 @@ def rescore_problem(request, course_id): ...@@ -561,21 +564,25 @@ def rescore_problem(request, course_id):
Takes either of the following query paremeters Takes either of the following query paremeters
- problem_to_reset is a urlname of a problem - 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 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')) 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] 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.") return HttpResponseBadRequest("Missing query parameters.")
if all_students and student_email: if all_students and student:
return HttpResponseBadRequest( 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) module_state_key = _msk_from_problem_urlname(course_id, problem_to_reset)
...@@ -583,9 +590,8 @@ def rescore_problem(request, course_id): ...@@ -583,9 +590,8 @@ def rescore_problem(request, course_id):
response_payload = {} response_payload = {}
response_payload['problem_to_reset'] = problem_to_reset response_payload['problem_to_reset'] = problem_to_reset
if student_email: if student:
response_payload['student_email'] = student_email response_payload['student'] = student_identifier
student = User.objects.get(email=student_email)
instructor_task.api.submit_rescore_problem_for_student(request, course_id, module_state_key, student) instructor_task.api.submit_rescore_problem_for_student(request, course_id, module_state_key, student)
response_payload['task'] = 'created' response_payload['task'] = 'created'
elif all_students: elif all_students:
...@@ -608,21 +614,22 @@ def list_instructor_tasks(request, course_id): ...@@ -608,21 +614,22 @@ def list_instructor_tasks(request, course_id):
Takes optional query paremeters. Takes optional query paremeters.
- With no arguments, lists running tasks. - With no arguments, lists running tasks.
- `problem_urlname` lists task history for problem - `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) history for problem AND student (intersection)
""" """
problem_urlname = strip_if_string(request.GET.get('problem_urlname', False)) 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( return HttpResponseBadRequest(
"student_email must accompany problem_urlname" "unique_student_identifier must accompany problem_urlname"
) )
if problem_urlname: if problem_urlname:
module_state_key = _msk_from_problem_urlname(course_id, problem_urlname) module_state_key = _msk_from_problem_urlname(course_id, problem_urlname)
if student_email: if student:
student = User.objects.get(email=student_email)
tasks = instructor_task.api.get_instructor_task_history(course_id, module_state_key, student) tasks = instructor_task.api.get_instructor_task_history(course_id, module_state_key, student)
else: else:
tasks = instructor_task.api.get_instructor_task_history(course_id, module_state_key) tasks = instructor_task.api.get_instructor_task_history(course_id, module_state_key)
......
...@@ -38,7 +38,7 @@ def instructor_dashboard_2(request, course_id): ...@@ -38,7 +38,7 @@ def instructor_dashboard_2(request, course_id):
raise Http404() raise Http404()
sections = [ sections = [
_section_course_info(course_id), _section_course_info(course_id, access),
_section_membership(course_id, access), _section_membership(course_id, access),
_section_student_admin(course_id, access), _section_student_admin(course_id, access),
_section_data_download(course_id), _section_data_download(course_id),
...@@ -67,18 +67,21 @@ section_display_name will be used to generate link titles in the nav bar. ...@@ -67,18 +67,21 @@ section_display_name will be used to generate link titles in the nav bar.
""" # pylint: disable=W0105 """ # pylint: disable=W0105
def _section_course_info(course_id): def _section_course_info(course_id, access):
""" Provide data for the corresponding dashboard section """ """ Provide data for the corresponding dashboard section """
course = get_course_by_id(course_id, depth=None) course = get_course_by_id(course_id, depth=None)
section_data = {} section_data = {
section_data['section_key'] = 'course_info' 'section_key': 'course_info',
section_data['section_display_name'] = _('Course Info') 'section_display_name': _('Course Info'),
section_data['course_id'] = course_id 'course_id': course_id,
section_data['course_display_name'] = course.display_name 'access': access,
section_data['enrollment_count'] = CourseEnrollment.objects.filter(course_id=course_id, is_active=1).count() 'course_display_name': course.display_name,
section_data['has_started'] = course.has_started() 'enrollment_count': CourseEnrollment.objects.filter(course_id=course_id).count(),
section_data['has_ended'] = course.has_ended() 'has_started': course.has_started(),
'has_ended': course.has_ended(),
'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': course_id}),
}
try: try:
advance = lambda memo, (letter, score): "{}: {}, ".format(letter, score) + memo advance = lambda memo, (letter, score): "{}: {}, ".format(letter, score) + memo
......
""" """
Tools for the instructor dashboard Tools for the instructor dashboard
""" """
from django.contrib.auth.models import User
def strip_if_string(value): def strip_if_string(value):
if isinstance(value, basestring): if isinstance(value, basestring):
return value.strip() return value.strip()
return value 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 @@ $ -> ...@@ -131,9 +131,11 @@ $ ->
initialText = $elem.html() initialText = $elem.html()
$elem.empty() $elem.empty()
_append = appended_id || "" _append = appended_id || ""
wmdInputId = "wmd-input#{_append}"
$wmdPanel = $("<div>").addClass("wmd-panel") $wmdPanel = $("<div>").addClass("wmd-panel")
.append($("<div>").attr("id", "wmd-button-bar#{_append}")) .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")) .append($("<div>").attr("id", "wmd-preview#{_append}").addClass("wmd-panel wmd-preview"))
$elem.append($wmdPanel) $elem.append($wmdPanel)
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
text-shadow: 0 1px 0 rgba(0, 0, 0, .3); 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); 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; border-color: #297095;
@include linear-gradient(top, #4fbbe4, #2090d0); @include linear-gradient(top, #4fbbe4, #2090d0);
} }
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6); 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); 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); @include linear-gradient(top, $white, #ddd);
} }
} }
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.6); 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); 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); background: -webkit-linear-gradient(top, #888, #666);
} }
} }
...@@ -217,8 +217,7 @@ body.discussion { ...@@ -217,8 +217,7 @@ body.discussion {
color: #eee; color: #eee;
@include transition(none); @include transition(none);
&:hover, &:hover, &:focus {
&.focused {
background-color: #666; background-color: #666;
} }
...@@ -305,7 +304,7 @@ body.discussion { ...@@ -305,7 +304,7 @@ body.discussion {
padding-bottom: 2px; padding-bottom: 2px;
height: 37px; height: 37px;
&:hover { &:hover, &:focus {
border-color: #222; border-color: #222;
} }
} }
...@@ -436,7 +435,7 @@ body.discussion { ...@@ -436,7 +435,7 @@ body.discussion {
height: 37px; height: 37px;
border-color: #333; border-color: #333;
&:hover { &:hover, &:focus {
border-color: #222; border-color: #222;
} }
} }
...@@ -714,7 +713,7 @@ body.discussion { ...@@ -714,7 +713,7 @@ body.discussion {
height: 100%; height: 100%;
background-color: #dedede; background-color: #dedede;
&:hover { &:hover, &:focus {
background-color: $white; background-color: $white;
} }
} }
...@@ -881,8 +880,7 @@ body.discussion { ...@@ -881,8 +880,7 @@ body.discussion {
display: none; display: none;
} }
&:hover, &:hover, &:focus {
&.focused {
background-color: #636363; background-color: #636363;
} }
...@@ -1015,7 +1013,7 @@ body.discussion { ...@@ -1015,7 +1013,7 @@ body.discussion {
color: #333; color: #333;
line-height: 17px; line-height: 17px;
&:hover { &:hover, &:focus {
@include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, .2)); @include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, .2));
color: #333; color: #333;
} }
...@@ -1067,7 +1065,7 @@ body.discussion { ...@@ -1067,7 +1065,7 @@ body.discussion {
line-height: 33px; line-height: 33px;
text-align: center; text-align: center;
&:hover { &:hover, &:focus {
background-image: none; background-image: none;
background-color: #e6e6e6; background-color: #e6e6e6;
} }
...@@ -1086,7 +1084,7 @@ body.discussion { ...@@ -1086,7 +1084,7 @@ body.discussion {
background-color: $white; background-color: $white;
@include clearfix; @include clearfix;
&:hover { &:hover, &:focus {
@include linear-gradient(top, rgba(255, 255, 255, .7), rgba(255, 255, 255, 0)); @include linear-gradient(top, rgba(255, 255, 255, .7), rgba(255, 255, 255, 0));
background-color: #eee; background-color: #eee;
} }
...@@ -2225,7 +2223,7 @@ body.discussion { ...@@ -2225,7 +2223,7 @@ body.discussion {
padding-bottom: 2px; padding-bottom: 2px;
border-color: #333; border-color: #333;
&:hover { &:hover, &:focus {
border-color: #222; border-color: #222;
} }
} }
...@@ -2522,7 +2520,7 @@ body.discussion { ...@@ -2522,7 +2520,7 @@ body.discussion {
margin-top: $baseline/2; margin-top: $baseline/2;
padding-bottom: 2px; padding-bottom: 2px;
&:hover { &:hover, &:focus {
border-color: #222; border-color: #222;
} }
} }
...@@ -2596,7 +2594,7 @@ body.discussion { ...@@ -2596,7 +2594,7 @@ body.discussion {
cursor: pointer; cursor: pointer;
} }
&:hover { &:hover, &:focus {
@include transition(opacity .2s linear 0s); @include transition(opacity .2s linear 0s);
opacity: 1.0; opacity: 1.0;
} }
...@@ -2659,7 +2657,7 @@ display:none; ...@@ -2659,7 +2657,7 @@ display:none;
cursor:pointer; cursor:pointer;
opacity: 0.8; opacity: 0.8;
&:hover { &:hover, &:focus {
@include transition(opacity .2s linear 0s); @include transition(opacity .2s linear 0s);
opacity: 1.0; opacity: 1.0;
} }
......
<%! from django.utils.translation import ugettext as _ %> <%! from django.utils.translation import ugettext as _ %>
% num_results = len(results) <% num_results = len(results) %>
% for (i,result) in enumerate(results): % for (i,result) in enumerate(results):
% if 'task_name' in result and 'result' in result: % if 'task_name' in result and 'result' in result:
<div class="combined-rubric-container" <div class="combined-rubric-container"
......
...@@ -240,7 +240,7 @@ function goto( mode) ...@@ -240,7 +240,7 @@ function goto( mode)
<hr width="40%" style="align:left"> <hr width="40%" style="align:left">
%endif %endif
<H2>${_("Student-specific grade inspection and adjustment")}</h2> <h2>${_("Student-specific grade inspection and adjustment")}</h2>
<p> <p>
${_("Specify the {platform_name} email address or username of a student here:").format(platform_name=settings.PLATFORM_NAME)} ${_("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"> <input type="text" name="unique_student_identifier">
......
...@@ -38,8 +38,21 @@ ...@@ -38,8 +38,21 @@
## ${ section_data['offline_grades'] } ## ${ section_data['offline_grades'] }
## </div> ## </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']): %if len(section_data['course_errors']):
<div class="course-errors-wrapper"> <div class="course-errors-wrapper">
<hr>
<p>
<div class="toggle-wrapper"> <div class="toggle-wrapper">
<h2 class="title">${_("Course Warnings")}:</h2> <h2 class="title">${_("Course Warnings")}:</h2>
<div class="triangle"></div> <div class="triangle"></div>
...@@ -52,5 +65,10 @@ ...@@ -52,5 +65,10 @@
</div> </div>
%endfor %endfor
</div> </div>
<p>
</div> </div>
<br>
%endif %endif
...@@ -2,25 +2,49 @@ ...@@ -2,25 +2,49 @@
<%page args="section_data"/> <%page args="section_data"/>
<div class="student-specific-container action-type-container"> <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> <div class="request-response-error"></div>
<p>
<input type="text" name="student-select" placeholder="${_("Student Email")}"> <!-- 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> <br>
<div class="progress-link-wrapper"> <div class="progress-link-wrapper">
<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> <a href="" class="progress-link" data-endpoint="${ section_data['get_student_progress_url_url'] }"> ${_("Student Progress Page")} </a>
</p>
</div> </div>
<br> <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="enroll" value="${_("Enroll")}" data-endpoint="${ section_data['enrollment_url'] }">
<input type="button" name="unenroll" value="${_("Unenroll")}" data-endpoint="${ section_data['enrollment_url'] }"> <input type="button" name="unenroll" value="${_("Unenroll")}" data-endpoint="${ section_data['enrollment_url'] }">
## <select class="problems"> </p>
## <option>Getting problems...</option> -->
## </select>
<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")}"> <input type="text" name="problem-select-single" placeholder="${_("Problem urlname")}">
</p>
<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( ${_('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>", location1="<tt>i4x://university/course/problem/problemname</tt>",
...@@ -29,20 +53,31 @@ ...@@ -29,20 +53,31 @@
urlname2="<tt>notaproblem/someothername</tt>") urlname2="<tt>notaproblem/someothername</tt>")
} }
</p> </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>
<p> ${_('You may also delete the entire state of a student for the specified module:')} </p> ${_("Next, select an action to perform for the given user and problem:")}
<input type="button" class="molly-guard" name="delete-state-single" value="${_("Delete Student State for Module")}" data-endpoint="${ section_data['reset_student_attempts_url'] }"> </p>
%endif
<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']: %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'] }"> <input type="button" name="rescore-problem-single" value="${_("Rescore Student Submission")}" data-endpoint="${ section_data['rescore_problem_url'] }">
%endif %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']: %if settings.MITX_FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS') and section_data['access']['instructor']:
<p> <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:")} "To see status for all tasks submitted for this problem and student, click on this button:")}
</p> </p>
...@@ -76,18 +111,11 @@ ...@@ -76,18 +111,11 @@
</p> </p>
<p> <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")}: "To see status for all tasks submitted for this problem, click on this button")}:
</p> </p>
<input type="button" name="task-history-all" value="${_("Show Background Task History for Problem")}" data-endpoint="${ section_data['list_instructor_tasks_url'] }"> <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> <div class="task-history-all-table"></div>
</p> </p>
</div> </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 %endif
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