Commit 100f6bf1 by Felix Sun

Began work on instructor view to hinting system.

Added moderation feature - you can now choose to hold all hints for moderator approval before showing.
parent 6f6a4517
...@@ -42,6 +42,14 @@ class CrowdsourceHinterFields(object): ...@@ -42,6 +42,14 @@ class CrowdsourceHinterFields(object):
user_voted = Boolean(help='Specifies if the user has voted on this problem or not.', user_voted = Boolean(help='Specifies if the user has voted on this problem or not.',
scope=Scope.user_state, default=False) scope=Scope.user_state, default=False)
moderate = String(help='''If 'True', then all hints must be approved by staff before
becoming visible.
This field is automatically populated from the xml metadata.''', scope=Scope.settings,
default='False')
mod_queue = Dict(help='''Contains hints that have not been approved by the staff yet. Structured
identically to the hints dictionary.''', scope=Scope.content, default={})
class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule): class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
''' An Xmodule that makes crowdsourced hints. ''' An Xmodule that makes crowdsourced hints.
...@@ -115,7 +123,11 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule): ...@@ -115,7 +123,11 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
print self.hints print self.hints
answer = self.ans_to_text(get) answer = self.ans_to_text(get)
# Look for a hint to give. # Look for a hint to give.
<<<<<<< HEAD
if answer not in self.hints: if answer not in self.hints:
=======
if (answer not in self.hints) or (len(self.hints[answer]) == 0):
>>>>>>> Began work on instructor view to hinting system.
# No hints to give. Return. # No hints to give. Return.
self.previous_answers += [(answer, (None, None, None))] self.previous_answers += [(answer, (None, None, None))]
return json.dumps({'contents': ' '}) return json.dumps({'contents': ' '})
...@@ -126,12 +138,23 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule): ...@@ -126,12 +138,23 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
if len(self.hints[answer]) == 1: if len(self.hints[answer]) == 1:
rand_hint_1 = '' rand_hint_1 = ''
rand_hint_2 = '' rand_hint_2 = ''
<<<<<<< HEAD
self.previous_answers += [(answer, (0, None, None))] self.previous_answers += [(answer, (0, None, None))]
elif len(self.hints[answer]) == 2: elif len(self.hints[answer]) == 2:
best_hint = self.hints[answer][0][0] best_hint = self.hints[answer][0][0]
rand_hint_1 = self.hints[answer][1][0] rand_hint_1 = self.hints[answer][1][0]
rand_hint_2 = '' rand_hint_2 = ''
self.previous_answers += [(answer, (0, 1, None))] self.previous_answers += [(answer, (0, 1, None))]
=======
self.previous_answers += [[answer, [best_hint_index, None, None]]]
elif n_hints == 2:
best_hint = self.hints[answer].values()[0][0]
best_hint_index = self.hints[answer].keys()[0]
rand_hint_1 = self.hints[answer].values()[1][0]
hint_index_1 = self.hints[answer].keys()[1]
rand_hint_2 = ''
self.previous_answers += [[answer, [best_hint_index, hint_index_1, None]]]
>>>>>>> Began work on instructor view to hinting system.
else: else:
hint_index_1, hint_index_2 = random.sample(xrange(len(self.hints[answer])), 2) hint_index_1, hint_index_2 = random.sample(xrange(len(self.hints[answer])), 2)
rand_hint_1 = self.hints[answer][hint_index_1][0] rand_hint_1 = self.hints[answer][hint_index_1][0]
...@@ -163,10 +186,20 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule): ...@@ -163,10 +186,20 @@ class CrowdsourceHinterModule(CrowdsourceHinterFields, XModule):
'" style="display:none"> Which hint was most helpful when you got the wrong answer of '\ '" style="display:none"> Which hint was most helpful when you got the wrong answer of '\
+ answer + '?' + answer + '?'
# Add each hint to the html string, with a vote button. # Add each hint to the html string, with a vote button.
for j, hint_id in enumerate(hints_offered): for hint_id in hints_offered:
if hint_id != None: if hint_id != None:
<<<<<<< HEAD
out += '<br /><input class="vote" data-answer="'+str(i)+'" data-hintno="'+str(j)+\ out += '<br /><input class="vote" data-answer="'+str(i)+'" data-hintno="'+str(j)+\
'" type="button" value="Vote"> ' + self.hints[answer][hint_id][0] '" type="button" value="Vote"> ' + self.hints[answer][hint_id][0]
=======
hint_id = str(hint_id)
try:
out += '<br /><input class="vote" data-answer="'+str(i)+'" data-hintno="'+hint_id+\
'" type="button" value="Vote"> ' + self.hints[answer][hint_id][0]
except KeyError:
# Sometimes, the hint that a user saw will have been deleted by the instructor.
continue
>>>>>>> Began work on instructor view to hinting system.
# Or, let the student create his own hint # Or, let the student create his own hint
...@@ -227,15 +260,45 @@ What would you say to help someone who got this wrong answer? ...@@ -227,15 +260,45 @@ What would you say to help someone who got this wrong answer?
answer = self.previous_answers[int(get['answer'])][0] answer = self.previous_answers[int(get['answer'])][0]
# Add the new hint to self.hints. (Awkward because a direct write # Add the new hint to self.hints. (Awkward because a direct write
# is necessary.) # is necessary.)
<<<<<<< HEAD
temp_dict = self.hints temp_dict = self.hints
temp_dict[answer].append([hint, 1]) # With one vote (the user himself). temp_dict[answer].append([hint, 1]) # With one vote (the user himself).
self.hints = temp_dict self.hints = temp_dict
=======
if self.moderate:
temp_dict = self.mod_queue
else:
temp_dict = self.hints
if answer in temp_dict:
temp_dict[answer][self.hint_pk] = [hint, 1] # With one vote (the user himself).
else:
temp_dict[answer] = {self.hint_pk: [hint, 1]}
self.hint_pk += 1
if self.moderate:
self.mod_queue = temp_dict
else:
self.hints = temp_dict
>>>>>>> Began work on instructor view to hinting system.
# Mark the user has having voted; reset previous_answers # Mark the user has having voted; reset previous_answers
self.user_voted = True self.user_voted = True
self.previous_answers = [] self.previous_answers = []
return json.dumps({'contents': 'Thank you for your hint!'}) return json.dumps({'contents': 'Thank you for your hint!'})
<<<<<<< HEAD
=======
def delete_hint(self, answer, hint_id):
'''
From the answer, delete the hint with hint_id.
Not designed to be accessed via POST request, for now.
-LIKELY DEPRECATED.
'''
temp_hints = self.hints
del temp_hints[answer][str(hint_id)]
self.hints = temp_hints
>>>>>>> Began work on instructor view to hinting system.
class CrowdsourceHinterDescriptor(CrowdsourceHinterFields, XmlDescriptor): class CrowdsourceHinterDescriptor(CrowdsourceHinterFields, XmlDescriptor):
module_class = CrowdsourceHinterModule module_class = CrowdsourceHinterModule
stores_state = True stores_state = True
......
'''
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()
<%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>
<%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>
\ No newline at end of file
...@@ -264,6 +264,9 @@ if settings.COURSEWARE_ENABLED: ...@@ -264,6 +264,9 @@ if settings.COURSEWARE_ENABLED:
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/instructor$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/instructor$',
'instructor.views.instructor_dashboard', name="instructor_dashboard"), '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$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/gradebook$',
'instructor.views.gradebook', name='gradebook'), 'instructor.views.gradebook', name='gradebook'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/grade_summary$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/grade_summary$',
......
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