Began work on instructor view to hinting system.
Added moderation feature - you can now choose to hold all hints for moderator approval before showing.
This commit is contained in:
committed by
Carlos Andrés Rocha
parent
415ea63bc0
commit
0c0de20a2f
138
lms/djangoapps/instructor/hint_manager.py
Normal file
138
lms/djangoapps/instructor/hint_manager.py
Normal file
@@ -0,0 +1,138 @@
|
||||
'''
|
||||
Views for hint management.
|
||||
'''
|
||||
|
||||
from collections import defaultdict
|
||||
import csv
|
||||
import json
|
||||
import logging
|
||||
from markupsafe import escape
|
||||
import os
|
||||
import re
|
||||
import requests
|
||||
from requests.status_codes import codes
|
||||
import urllib
|
||||
from collections import OrderedDict
|
||||
|
||||
from StringIO import StringIO
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.http import HttpResponse, Http404
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
from django.views.decorators.cache import cache_control
|
||||
from mitxmako.shortcuts import render_to_response, render_to_string
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from courseware.courses import get_course_with_access
|
||||
from courseware.models import XModuleContentField
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def hint_manager(request, course_id):
|
||||
try:
|
||||
course = get_course_with_access(request.user, course_id, 'staff', depth=None)
|
||||
except Http404:
|
||||
out = 'Sorry, but students are not allowed to access the hint manager!'
|
||||
return
|
||||
if request.method == 'GET':
|
||||
out = get_hints(request, course_id, 'mod_queue')
|
||||
return render_to_response('courseware/hint_manager.html', out)
|
||||
field = request.POST['field']
|
||||
if not (field == 'mod_queue' or field == 'hints'):
|
||||
# Invalid field. (Don't let users continue - they may overwrite other db's)
|
||||
return
|
||||
if request.POST['op'] == 'delete hints':
|
||||
delete_hints(request, course_id, field)
|
||||
if request.POST['op'] == 'switch fields':
|
||||
pass
|
||||
if request.POST['op'] == 'change votes':
|
||||
change_votes(request, course_id, field)
|
||||
rendered_html = render_to_string('courseware/hint_manager_inner.html', get_hints(request, course_id, field))
|
||||
return HttpResponse(json.dumps({'success': True, 'contents': rendered_html}))
|
||||
|
||||
|
||||
|
||||
def get_hints(request, course_id, field):
|
||||
# field indicates the database entry that we are modifying.
|
||||
# Right now, the options are 'hints' or 'mod_queue'.
|
||||
# DON'T TRUST field attributes that come from ajax. Use an if statement
|
||||
# to make sure the field is valid before plugging into functions.
|
||||
|
||||
out = ''
|
||||
if field == 'mod_queue':
|
||||
other_field = 'hints'
|
||||
field_label = 'Hints Awaiting Moderation'
|
||||
other_field_label = 'Approved Hints'
|
||||
elif field == 'hints':
|
||||
other_field = 'mod_queue'
|
||||
field_label = 'Approved Hints'
|
||||
other_field_label = 'Hints Awaiting Moderation'
|
||||
chopped_id = '/'.join(course_id.split('/')[:-1])
|
||||
chopped_id = re.escape(chopped_id)
|
||||
all_hints = XModuleContentField.objects.filter(field_name=field, definition_id__regex=chopped_id)
|
||||
for problem in all_hints:
|
||||
out += '<h2> Problem: ' + problem.definition_id + '</h2>'
|
||||
for answer, hint_dict in json.loads(problem.value).items():
|
||||
out += '<h4> Answer: ' + answer + '</h4>'
|
||||
for pk, hint in hint_dict.items():
|
||||
out += '<p data-problem="'\
|
||||
+ problem.definition_id + '" data-pk="' + str(pk) + '" data-answer="'\
|
||||
+ answer + '">'
|
||||
out += '<input class="hint-select" type="checkbox"/>' + hint[0] + \
|
||||
'<br /> Votes: <input type="text" class="votes" value="' + str(hint[1]) + '"></input>'
|
||||
out += '</p>'
|
||||
out += '''<h4> Add a hint to this problem </h4>
|
||||
Answer (exact formatting):
|
||||
<input type="text" id="new-hint-answer-''' + problem.definition_id \
|
||||
+ '"/> <br /> Hint: <br /><textarea cols="50" style="height:200px" id="new-hint-' + problem.definition_id \
|
||||
+ '"></textarea> <br /> <button class="submit-new-hint" data-problem="' + problem.definition_id \
|
||||
+ '"> Submit </button><br />'
|
||||
|
||||
|
||||
out += '<button id="hint-delete"> Delete selected </button> <button id="update-votes"> Update votes </button>'
|
||||
render_dict = {'out': out,
|
||||
'field': field,
|
||||
'other_field': other_field,
|
||||
'field_label': field_label,
|
||||
'other_field_label': other_field_label,
|
||||
'all_hints': all_hints}
|
||||
return render_dict
|
||||
|
||||
def delete_hints(request, course_id, field):
|
||||
'''
|
||||
Deletes the hints specified by the [problem_defn_id, answer, pk] tuples in the numbered
|
||||
fields of request.POST.
|
||||
'''
|
||||
for key in request.POST:
|
||||
if key == 'op' or key == 'field':
|
||||
continue
|
||||
problem_id, answer, pk = request.POST.getlist(key)
|
||||
# Can be optimized - sort the delete list by problem_id, and load each problem
|
||||
# from the database only once.
|
||||
this_problem = XModuleContentField.objects.get(field_name=field, definition_id=problem_id)
|
||||
problem_dict = json.loads(this_problem.value)
|
||||
del problem_dict[answer][pk]
|
||||
this_problem.value = json.dumps(problem_dict)
|
||||
this_problem.save()
|
||||
|
||||
def change_votes(request, course_id, field):
|
||||
'''
|
||||
Updates the number of votes. The numbered fields of request.POST contain
|
||||
[problem_id, answer, pk, new_votes] tuples.
|
||||
- Very similar to delete_hints. Is there a way to merge them? Nah, too complicated.
|
||||
'''
|
||||
for key in request.POST:
|
||||
if key == 'op' or key == 'field':
|
||||
continue
|
||||
problem_id, answer, pk, new_votes = request.POST.getlist(key)
|
||||
this_problem = XModuleContentField.objects.get(field_name=field, definition_id=problem_id)
|
||||
problem_dict = json.loads(this_problem.value)
|
||||
problem_dict[answer][pk][1] = new_votes
|
||||
this_problem.value = json.dumps(problem_dict)
|
||||
this_problem.save()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
89
lms/templates/courseware/hint_manager.html
Normal file
89
lms/templates/courseware/hint_manager.html
Normal file
@@ -0,0 +1,89 @@
|
||||
<%inherit file="/main.html" />
|
||||
<%namespace name='static' file='/static_content.html'/>
|
||||
<%namespace name="content" file="/courseware/hint_manager_inner.html"/>
|
||||
|
||||
|
||||
<%block name="headextra">
|
||||
<%static:css group='course'/>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.axislabels.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/jquery-jvectormap-1.1.1/jquery-jvectormap-1.1.1.min.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/jquery-jvectormap-1.1.1/jquery-jvectormap-world-mill-en.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/course_groups/cohorts.js')}"></script>
|
||||
|
||||
<script>
|
||||
function setup() {
|
||||
field = $("#field-label").html()
|
||||
changed_votes = []
|
||||
$(".votes").live('input', function() {
|
||||
changed_votes.push($(this))
|
||||
});
|
||||
|
||||
$("#hint-delete").click(function(){
|
||||
var data_dict = {'op': 'delete hints',
|
||||
'field': field}
|
||||
var i = 1
|
||||
$(".hint-select").each(function(){
|
||||
if ($(this).is(":checked")) {
|
||||
data_dict[i] = [$(this).parent().attr("data-problem"),
|
||||
$(this).parent().attr("data-answer"),
|
||||
$(this).parent().attr("data-pk")];
|
||||
}
|
||||
});
|
||||
$.ajax(window.location.pathname, {
|
||||
type: "POST",
|
||||
data: data_dict,
|
||||
success: update_contents
|
||||
});
|
||||
});
|
||||
|
||||
$("#update-votes").click(function(){
|
||||
var data_dict = {'op': 'change votes',
|
||||
'field': field}
|
||||
for (var i=0; i<changed_votes.length; i++) {
|
||||
data_dict[i] = [$(changed_votes[i]).parent().attr("data-problem"),
|
||||
$(changed_votes[i]).parent().attr("data-answer"),
|
||||
$(changed_votes[i]).parent().attr("data-pk"),
|
||||
$(changed_votes[i]).val()];
|
||||
}
|
||||
$.ajax(window.location.pathname, {
|
||||
type: "POST",
|
||||
data: data_dict,
|
||||
success: update_contents
|
||||
});
|
||||
});
|
||||
|
||||
$("#switch-fields").click(function(){
|
||||
out_dict = {'op': 'switch fields',
|
||||
'field': $(this).attr("other-field")};
|
||||
$.ajax(window.location.pathname, {
|
||||
type: "POST",
|
||||
data: out_dict,
|
||||
success: update_contents
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
$(document).ready(setup);
|
||||
|
||||
function update_contents(data, status, jqXHR) {
|
||||
$('.instructor-dashboard-content').html(data.contents);
|
||||
setup();
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</%block>
|
||||
|
||||
<section class="container">
|
||||
<div class="instructor-dashboard-wrapper">
|
||||
|
||||
<section class="instructor-dashboard-content">
|
||||
${content.main()}
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
39
lms/templates/courseware/hint_manager_inner.html
Normal file
39
lms/templates/courseware/hint_manager_inner.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<%block name="main">
|
||||
<div id="field-label" style="display:none"> ${field} </div>
|
||||
|
||||
<h1> ${field_label} </h1>
|
||||
Switch to <a id="switch-fields" other-field="${other_field}"> ${other_field_label} </a>
|
||||
|
||||
|
||||
% for problem in all_hints:
|
||||
<h2> Problem: ${problem.definition_id} </h2>
|
||||
<%
|
||||
import json
|
||||
loaded_json = json.loads(problem.value).items()
|
||||
%>
|
||||
% for answer, hint_dict in loaded_json:
|
||||
<h4> Answer: ${answer} </h4>
|
||||
% for pk, hint in hint_dict.items():
|
||||
<p data-problem="${problem.definition_id}" data-pk="${pk}" data-answer="${answer}">
|
||||
<input class="hint-select" type="checkbox"/> ${hint[0]}
|
||||
<br />
|
||||
Votes: <input type="text" class="votes" value="${str(hint[1])}"></input>
|
||||
</p>
|
||||
% endfor
|
||||
% endfor
|
||||
|
||||
<h4> Add a hint to this problem </h4>
|
||||
Answer (exact formatting):
|
||||
<input type="text" id="new-hint-answer-${problem.definition_id}"/>
|
||||
<br />
|
||||
Hint: <br />
|
||||
<textarea cols="50" style="height:200px" id="new-hint-${problem.definition_id}"></textarea>
|
||||
<br />
|
||||
<button class="submit-new-hint" data-problem="${problem.definition_id}"> Submit </button>
|
||||
<br />
|
||||
% endfor
|
||||
|
||||
<button id="hint-delete"> Delete selected </button> <button id="update-votes"> Update votes </button>
|
||||
|
||||
|
||||
</%block>
|
||||
@@ -264,6 +264,9 @@ if settings.COURSEWARE_ENABLED:
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/instructor$',
|
||||
'instructor.views.instructor_dashboard', name="instructor_dashboard"),
|
||||
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/hint_manager$',
|
||||
'instructor.hint_manager.hint_manager', name="hint_manager"),
|
||||
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/gradebook$',
|
||||
'instructor.views.gradebook', name='gradebook'),
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/grade_summary$',
|
||||
|
||||
Reference in New Issue
Block a user