Commit fbf23f9e by Tim Krones

Clean up.

parent dd04ee8c
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
import json import json
import logging import logging
from lazy.lazy import lazy
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from xblock.core import XBlock from xblock.core import XBlock
...@@ -126,7 +127,7 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin ...@@ -126,7 +127,7 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin
help=_( help=_(
'Claims and questions that should be included in the plot. ' 'Claims and questions that should be included in the plot. '
'Each line defines a triple of the form "claim, q1, q2", ' 'Each line defines a triple of the form "claim, q1, q2", '
'where "claim" is arbitrary text that represents a claim (must be quoted using double-quotes), ' 'where "claim" is arbitrary text that represents a claim, '
'and "q1" and "q2" are IDs of scale questions. ' 'and "q1" and "q2" are IDs of scale questions. '
), ),
default="", default="",
...@@ -139,19 +140,26 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin ...@@ -139,19 +140,26 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin
'q1_label', 'q2_label', 'q3_label', 'q4_label', 'claims' 'q1_label', 'q2_label', 'q3_label', 'q4_label', 'claims'
) )
@lazy
def course_id(self):
return unicode(getattr(self.runtime, 'course_id', 'course_id'))
@lazy
def course_key_str(self):
course_key = CourseKey.from_string(self.course_id)
return unicode(course_key)
@property @property
def default_claims(self): def default_claims(self):
if not self.claims: return self._get_claims(self._get_default_response)
return []
course_id = unicode(getattr(self.runtime, 'course_id', 'course_id')) @property
course_key = CourseKey.from_string(course_id) def average_claims(self):
course_key_str = unicode(course_key) return self._get_claims(self._get_average_response)
user_service = self.runtime.service(self, 'user') def _get_claims(self, response_function):
user = user_service.get_current_user() if not self.claims:
username = user.opt_attrs.get('edx-platform.username') return []
anonymous_user_id = user_service.get_anonymous_user_id(username, course_id)
mentoring_block = self.get_parent().get_parent() mentoring_block = self.get_parent().get_parent()
question_ids, questions = mentoring_block.question_ids, mentoring_block.questions question_ids, questions = mentoring_block.question_ids, mentoring_block.questions
...@@ -161,66 +169,45 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin ...@@ -161,66 +169,45 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin
r1, r2 = None, None r1, r2 = None, None
for question_id, question in zip(question_ids, questions): for question_id, question in zip(question_ids, questions):
if question.name == q1: if question.name == q1:
r1 = self._default_response(course_key_str, question, question_id, anonymous_user_id) r1 = response_function(self.course_key_str, question, question_id)
if question.name == q2: if question.name == q2:
r2 = self._default_response(course_key_str, question, question_id, anonymous_user_id) r2 = response_function(self.course_key_str, question, question_id)
if r1 is not None and r2 is not None: if r1 is not None and r2 is not None:
break break
claims.append([claim, r1, r2]) claims.append([claim, r1, r2])
return claims return claims
def _default_response(self, course_key_str, question, question_id, user_id): def _get_default_response(self, course_key_str, question, question_id):
from .tasks import _get_answer # Import here to avoid circular dependency from .tasks import _get_answer # Import here to avoid circular dependency
# 1. Obtain block_type for question # 1. Obtain ID of current user that can be used to look up submissions
user_service = self.runtime.service(self, 'user')
user = user_service.get_current_user()
username = user.opt_attrs.get('edx-platform.username')
anonymous_user_id = user_service.get_anonymous_user_id(username, self.course_id)
# 2. Obtain block_type for question
question_type = question.scope_ids.block_type question_type = question.scope_ids.block_type
# 2. Obtain submissions for question using course_key_str, block_id, block_type, user_id # 3. Obtain latest submission for question
student_dict = { student_dict = {
'student_id': user_id, 'student_id': anonymous_user_id,
'course_id': course_key_str, 'course_id': course_key_str,
'item_id': question_id, 'item_id': question_id,
'item_type': question_type, 'item_type': question_type,
} }
submissions = sub_api.get_submissions(student_dict, limit=1) # Gets latest submission submissions = sub_api.get_submissions(student_dict, limit=1)
# 3. Extract response from latest submission for question # 4. Extract response from latest submission for question
answer_cache = {} answer_cache = {}
for submission in submissions: for submission in submissions:
answer = _get_answer(question, submission, answer_cache) answer = _get_answer(question, submission, answer_cache)
return int(answer) return int(answer)
@property def _get_average_response(self, course_key_str, question, question_id):
def average_claims(self):
if not self.claims:
return []
course_id = unicode(getattr(self.runtime, 'course_id', 'course_id'))
course_key = CourseKey.from_string(course_id)
course_key_str = unicode(course_key)
mentoring_block = self.get_parent().get_parent()
question_ids, questions = mentoring_block.question_ids, mentoring_block.questions
claims = []
for line in self.claims.split('\n'):
claim, q1, q2 = line.split(', ')
r1, r2 = None, None
for question_id, question in zip(question_ids, questions):
if question.name == q1:
r1 = self._average_response(course_key_str, question, question_id)
if question.name == q2:
r2 = self._average_response(course_key_str, question, question_id)
if r1 is not None and r2 is not None:
break
claims.append([claim, r1, r2])
return claims
def _average_response(self, course_key_str, question, question_id):
from .tasks import _get_answer # Import here to avoid circular dependency from .tasks import _get_answer # Import here to avoid circular dependency
# 1. Obtain block_type for question # 1. Obtain block_type for question
question_type = question.scope_ids.block_type question_type = question.scope_ids.block_type
# 2. Obtain submissions for question using course_key_str, block_id, block_type # 2. Obtain latest submissions for question
submissions = sub_api.get_all_submissions(course_key_str, question_id, question_type) # Gets latest submissions submissions = sub_api.get_all_submissions(course_key_str, question_id, question_type)
# 3. Extract responses from submissions for question and sum them up # 3. Extract responses from latest submissions for question and sum them up
answer_cache = {} answer_cache = {}
response_total = 0 response_total = 0
num_submissions = 0 # Can't use len(submissions) because submissions is a generator num_submissions = 0 # Can't use len(submissions) because submissions is a generator
...@@ -245,16 +232,6 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin ...@@ -245,16 +232,6 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin
'average_claims': self.average_claims, 'average_claims': self.average_claims,
} }
def clean_studio_edits(self, data):
# FIXME: Use this to clean data.claims (remove leading/trailing whitespace, etc.)
pass
def validate_field_data(self, validation, data):
# FIXME: Use this to validate data.claims:
# - Each line should be of the form "claim, q1, q2" (no quotes)
# - Entries for "claim", "q1", "q2" must point to existing blocks
pass
def author_preview_view(self, context): def author_preview_view(self, context):
return Fragment( return Fragment(
u"<p>{}</p>".format( u"<p>{}</p>".format(
......
function PlotBlock(runtime, element) { function PlotBlock(runtime, element) {
// Plot
// Define margins // Define margins
var margins = {top: 20, right: 20, bottom: 20, left: 20}; var margins = {top: 20, right: 20, bottom: 20, left: 20};
...@@ -52,28 +54,31 @@ function PlotBlock(runtime, element) { ...@@ -52,28 +54,31 @@ function PlotBlock(runtime, element) {
.attr("transform", "translate(" + plotWidth / 2 + ", 0)") .attr("transform", "translate(" + plotWidth / 2 + ", 0)")
.call(yAxis); .call(yAxis);
// Buttons
var defaultButton = $('.plot-default', element);
var averageButton = $('.plot-average', element);
var quadrantsButton = $('.plot-quadrants', element);
// Claims // Claims
var defaultClaims = $('.plot-default', element).data('claims'); var defaultClaims = defaultButton.data('claims');
var averageClaims = $('.plot-average', element).data('claims'); var averageClaims = averageButton.data('claims');
// Colors // Colors
var defaultColor = $('.plot-default', element).data('point-color'); var defaultColor = defaultButton.data('point-color');
var averageColor = $('.plot-average', element).data('point-color'); var averageColor = averageButton.data('point-color');
// Quadrant labels // Quadrant labels
var q1Label = $('.plot-quadrants', element).data('q1-label'); var q1Label = quadrantsButton.data('q1-label');
var q2Label = $('.plot-quadrants', element).data('q2-label'); var q2Label = quadrantsButton.data('q2-label');
var q3Label = $('.plot-quadrants', element).data('q3-label'); var q3Label = quadrantsButton.data('q3-label');
var q4Label = $('.plot-quadrants', element).data('q4-label'); var q4Label = quadrantsButton.data('q4-label');
// Event handlers // Event handlers
var defaultButton = $('.plot-default', element);
var averageButton = $('.plot-average', element);
function toggleOverlay(claims, color, klass, refresh) { function toggleOverlay(claims, color, klass, refresh) {
var selector = "." + klass; var selector = "." + klass;
var selection = svgContainer.selectAll(selector); var selection = svgContainer.selectAll(selector);
...@@ -122,18 +127,6 @@ function PlotBlock(runtime, element) { ...@@ -122,18 +127,6 @@ function PlotBlock(runtime, element) {
} }
} }
defaultButton.on('click', function(event, refresh) {
toggleOverlay(defaultClaims, defaultColor, 'claim-default', refresh);
toggleBorderColor(this, defaultColor, refresh);
});
averageButton.on('click', function(event) {
toggleOverlay(averageClaims, averageColor, 'claim-average');
toggleBorderColor(this, averageColor);
});
var quadrantsButton = $('.plot-quadrants', element);
function toggleQuadrantLabels() { function toggleQuadrantLabels() {
var selection = svgContainer.selectAll(".quadrant-label"); var selection = svgContainer.selectAll(".quadrant-label");
var quadrantLabelsOn = quadrantsButton.val() === 'On'; var quadrantLabelsOn = quadrantsButton.val() === 'On';
...@@ -170,15 +163,24 @@ function PlotBlock(runtime, element) { ...@@ -170,15 +163,24 @@ function PlotBlock(runtime, element) {
} }
} }
defaultButton.on('click', function(event, refresh) {
toggleOverlay(defaultClaims, defaultColor, 'claim-default', refresh);
toggleBorderColor(this, defaultColor, refresh);
});
averageButton.on('click', function(event) {
toggleOverlay(averageClaims, averageColor, 'claim-average');
toggleBorderColor(this, averageColor);
});
quadrantsButton.on('click', function() { quadrantsButton.on('click', function() {
toggleQuadrantLabels(); toggleQuadrantLabels();
}); });
// Quadrant labels are off initially; color of button for toggling them should reflect this // Quadrant labels are off initially; color of button for toggling them should reflect this
quadrantsButton.css("border-color", "red"); quadrantsButton.css("border-color", "red");
// Functions that can be called from the outside // API
var dataXHR; var dataXHR;
......
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