Commit bee4e702 by Jonathan Piacenti

Allow students to download a copy of their answer table.

parent 6aaa3aba
...@@ -272,6 +272,8 @@ class AnswerRecapBlock(AnswerMixin, StudioEditableXBlockMixin, XBlock): ...@@ -272,6 +272,8 @@ class AnswerRecapBlock(AnswerMixin, StudioEditableXBlockMixin, XBlock):
) )
editable_fields = ('name', 'display_name', 'description') editable_fields = ('name', 'display_name', 'description')
css_path = 'public/css/answer.css'
@property @property
def student_input(self): def student_input(self):
if self.name: if self.name:
...@@ -287,7 +289,7 @@ class AnswerRecapBlock(AnswerMixin, StudioEditableXBlockMixin, XBlock): ...@@ -287,7 +289,7 @@ class AnswerRecapBlock(AnswerMixin, StudioEditableXBlockMixin, XBlock):
html = loader.render_template('templates/html/answer_read_only.html', context) html = loader.render_template('templates/html/answer_read_only.html', context)
fragment = Fragment(html) fragment = Fragment(html)
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/answer.css')) fragment.add_css_url(self.runtime.local_resource_url(self, self.css_path))
return fragment return fragment
def student_view(self, context=None): def student_view(self, context=None):
......
...@@ -58,6 +58,39 @@ def _(text): ...@@ -58,6 +58,39 @@ def _(text):
# Classes ########################################################### # Classes ###########################################################
class ExportMixin(object):
"""
Used by blocks which need to provide a downloadable export.
"""
def _get_user_full_name(self):
"""
Get the full name of the current user, for the downloadable report.
"""
user_service = self.runtime.service(self, 'user')
if user_service:
return user_service.get_current_user().full_name
return ""
def _get_course_name(self):
"""
Get the name of the current course, for the downloadable report.
"""
try:
course_key = self.scope_ids.usage_id.course_key
except AttributeError:
return "" # We are not in an edX runtime
try:
course_root_key = course_key.make_usage_key('course', 'course')
return self.runtime.get_block(course_root_key).display_name
except Exception: # ItemNotFoundError most likely, but we can't import that exception in non-edX environments
# We may be on old mongo:
try:
course_root_key = course_key.make_usage_key('course', course_key.run)
return self.runtime.get_block(course_root_key).display_name
except Exception:
return ""
class ColorRule(object): class ColorRule(object):
""" """
A rule used to conditionally set colors A rule used to conditionally set colors
...@@ -155,7 +188,7 @@ class InvalidUrlName(ValueError): ...@@ -155,7 +188,7 @@ class InvalidUrlName(ValueError):
@XBlock.needs("i18n") @XBlock.needs("i18n")
@XBlock.wants("user") @XBlock.wants("user")
class DashboardBlock(StudioEditableXBlockMixin, XBlock): class DashboardBlock(StudioEditableXBlockMixin, ExportMixin, XBlock):
""" """
A block to summarize self-assessment results. A block to summarize self-assessment results.
""" """
...@@ -260,7 +293,7 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock): ...@@ -260,7 +293,7 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock):
'color_rules', 'visual_rules', 'visual_title', 'visual_desc', 'header_html', 'footer_html', 'color_rules', 'visual_rules', 'visual_title', 'visual_desc', 'header_html', 'footer_html',
) )
css_path = 'public/css/dashboard.css' css_path = 'public/css/dashboard.css'
js_path = 'public/js/dashboard.js' js_path = 'public/js/review_blocks.js'
def get_mentoring_blocks(self, mentoring_ids, ignore_errors=True): def get_mentoring_blocks(self, mentoring_ids, ignore_errors=True):
""" """
...@@ -343,34 +376,6 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock): ...@@ -343,34 +376,6 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock):
return rule.color_str return rule.color_str
return None return None
def _get_user_full_name(self):
"""
Get the full name of the current user, for the downloadable report.
"""
user_service = self.runtime.service(self, 'user')
if user_service:
return user_service.get_current_user().full_name
return ""
def _get_course_name(self):
"""
Get the name of the current course, for the downloadable report.
"""
try:
course_key = self.scope_ids.usage_id.course_key
except AttributeError:
return "" # We are not in an edX runtime
try:
course_root_key = course_key.make_usage_key('course', 'course')
return self.runtime.get_block(course_root_key).display_name
except Exception: # ItemNotFoundError most likely, but we can't import that exception in non-edX environments
# We may be on old mongo:
try:
course_root_key = course_key.make_usage_key('course', course_key.run)
return self.runtime.get_block(course_root_key).display_name
except Exception:
return ""
def _get_problem_questions(self, mentoring_block): def _get_problem_questions(self, mentoring_block):
""" Generator returning only children of specified block that are MCQs """ """ Generator returning only children of specified block that are MCQs """
for child_id in mentoring_block.children: for child_id in mentoring_block.children:
...@@ -469,7 +474,11 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock): ...@@ -469,7 +474,11 @@ class DashboardBlock(StudioEditableXBlockMixin, XBlock):
fragment = Fragment(html) fragment = Fragment(html)
fragment.add_css_url(self.runtime.local_resource_url(self, self.css_path)) fragment.add_css_url(self.runtime.local_resource_url(self, self.css_path))
fragment.add_javascript_url(self.runtime.local_resource_url(self, self.js_path)) fragment.add_javascript_url(self.runtime.local_resource_url(self, self.js_path))
fragment.initialize_js('PBDashboardBlock', {'reportTemplate': report_template}) fragment.initialize_js(
'PBDashboardBlock', {
'reportTemplate': report_template,
'reportContentSelector': '.dashboard-report'
})
return fragment return fragment
def validate_field_data(self, validation, data): def validate_field_data(self, validation, data):
......
function MentoringTableBlock(runtime, element) {
// Display an exceprt for long answers, with a "more" link to display the full text
$('.answer-table', element).shorten({
moreText: 'more',
lessText: 'less',
showChars: '500'
});
return {};
}
// Client side code for the Problem Builder Dashboard XBlock // Client side code for the Problem Builder Dashboard XBlock
// So far, this code is only used to generate a downloadable report. // So far, this code is only used to generate a downloadable report.
function PBDashboardBlock(runtime, element, initData) { function ExportBase(runtime, element, initData) {
"use strict"; "use strict";
var reportTemplate = initData.reportTemplate; var reportTemplate = initData.reportTemplate;
...@@ -32,7 +32,7 @@ function PBDashboardBlock(runtime, element, initData) { ...@@ -32,7 +32,7 @@ function PBDashboardBlock(runtime, element, initData) {
// Download Report: // Download Report:
// Change the URL to a data: URI before continuing with the click event. // Change the URL to a data: URI before continuing with the click event.
if ($(this).attr('href').charAt(0) == '#') { if ($(this).attr('href').charAt(0) == '#') {
var $report = $('.dashboard-report', element).clone(); var $report = $(initData.reportContentSelector, element).clone();
// Convert all images in $report to data URIs: // Convert all images in $report to data URIs:
$report.find('image').each(function() { $report.find('image').each(function() {
var origURL = $(this).attr('xlink:href'); var origURL = $(this).attr('xlink:href');
...@@ -49,3 +49,17 @@ function PBDashboardBlock(runtime, element, initData) { ...@@ -49,3 +49,17 @@ function PBDashboardBlock(runtime, element, initData) {
var $downloadLink = $('.report-download-link', element); var $downloadLink = $('.report-download-link', element);
$downloadLink.on('click', downloadReport); $downloadLink.on('click', downloadReport);
} }
function PBDashboardBlock(runtime, element, initData) {
new ExportBase(runtime, element, initData);
}
function MentoringTableBlock(runtime, element, initData) {
// Display an excerpt for long answers, with a "more" link to display the full text
$('.answer-table', element).shorten({
moreText: 'more',
lessText: 'less',
showChars: '500'
});
new ExportBase(runtime, element, initData)
}
...@@ -24,13 +24,15 @@ import errno ...@@ -24,13 +24,15 @@ import errno
from xblock.core import XBlock from xblock.core import XBlock
from xblock.fields import Scope, String from xblock.fields import Scope, String, Boolean
from xblock.fragment import Fragment from xblock.fragment import Fragment
from xblockutils.resources import ResourceLoader from xblockutils.resources import ResourceLoader
from xblockutils.studio_editable import StudioEditableXBlockMixin, StudioContainerXBlockMixin from xblockutils.studio_editable import StudioEditableXBlockMixin, StudioContainerXBlockMixin
# Globals ########################################################### # Globals ###########################################################
from problem_builder import AnswerRecapBlock
from problem_builder.dashboard import ExportMixin
loader = ResourceLoader(__name__) loader = ResourceLoader(__name__)
...@@ -42,7 +44,8 @@ def _(text): ...@@ -42,7 +44,8 @@ def _(text):
# Classes ########################################################### # Classes ###########################################################
class MentoringTableBlock(StudioEditableXBlockMixin, StudioContainerXBlockMixin, XBlock): @XBlock.wants("user")
class MentoringTableBlock(StudioEditableXBlockMixin, StudioContainerXBlockMixin, ExportMixin, XBlock):
""" """
Table-type display of information from mentoring blocks Table-type display of information from mentoring blocks
...@@ -66,9 +69,18 @@ class MentoringTableBlock(StudioEditableXBlockMixin, StudioContainerXBlockMixin, ...@@ -66,9 +69,18 @@ class MentoringTableBlock(StudioEditableXBlockMixin, StudioContainerXBlockMixin,
{"display_name": "Immunity Map", "value": "immunity-map"}, {"display_name": "Immunity Map", "value": "immunity-map"},
], ],
) )
editable_fields = ("type", ) editable_fields = ("type", "allow_download")
allow_download = Boolean(
display_name=_("Allow Download"),
help=_("Allow students to download a copy of the table for themselves."),
default=False,
scope=Scope.content
)
has_children = True has_children = True
css_path = 'public/css/mentoring-table.css'
js_path = 'public/js/review_blocks.js'
def student_view(self, context): def student_view(self, context):
context = context.copy() if context else {} context = context.copy() if context else {}
fragment = Fragment() fragment = Fragment()
...@@ -83,6 +95,7 @@ class MentoringTableBlock(StudioEditableXBlockMixin, StudioContainerXBlockMixin, ...@@ -83,6 +95,7 @@ class MentoringTableBlock(StudioEditableXBlockMixin, StudioContainerXBlockMixin,
fragment.add_frag_resources(child_frag) fragment.add_frag_resources(child_frag)
context['header_values'] = header_values if any(header_values) else None context['header_values'] = header_values if any(header_values) else None
context['content_values'] = content_values context['content_values'] = content_values
context['allow_download'] = self.allow_download
if self.type: if self.type:
# Load an optional background image: # Load an optional background image:
...@@ -96,11 +109,23 @@ class MentoringTableBlock(StudioEditableXBlockMixin, StudioContainerXBlockMixin, ...@@ -96,11 +109,23 @@ class MentoringTableBlock(StudioEditableXBlockMixin, StudioContainerXBlockMixin,
else: else:
raise raise
report_template = loader.render_template('templates/html/mentoring-table-report.html', {
'title': self.display_name,
'css': loader.load_unicode(AnswerRecapBlock.css_path) + loader.load_unicode(self.css_path),
'student_name': self._get_user_full_name(),
'course_name': self._get_course_name(),
})
fragment.add_content(loader.render_template('templates/html/mentoring-table.html', context)) fragment.add_content(loader.render_template('templates/html/mentoring-table.html', context))
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/mentoring-table.css')) fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/mentoring-table.css'))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/vendor/jquery-shorten.js')) fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/vendor/jquery-shorten.js'))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/mentoring-table.js')) fragment.add_javascript_url(self.runtime.local_resource_url(self, self.js_path))
fragment.initialize_js('MentoringTableBlock') fragment.initialize_js(
'MentoringTableBlock', {
'reportContentSelector': '.mentoring-table-container',
'reportTemplate': report_template,
}
)
return fragment return fragment
......
{% load i18n %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ title }}</title>
<style>
body {
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
{{css|safe}}
</style>
</head>
<body>
<div class="mentoring">
<div class="identification">
{% if student_name %}{% trans "Student" %}: {{student_name}}<br>{% endif %}
{% if course_name %}{% trans "Course" %}: {{course_name}}<br>{% endif %}
{% trans "Date" %}: {% now "DATE_FORMAT" %}<br>
</div>
REPORT_GOES_HERE
</div>
</body>
</html>
<div class="mentoring-table {{ self.type }}" style="background-image: url({{ bg_image_url }})"> {% load i18n %}
<div class="mentoring-table-container">
<div class="mentoring-table {{ self.type }}" style="background-image: url({{ bg_image_url }})">
<div class="cont-text-sr">{{ bg_image_description }}</div> <div class="cont-text-sr">{{ bg_image_description }}</div>
<table> <table>
{% if header_values %} {% if header_values %}
...@@ -20,4 +22,8 @@ ...@@ -20,4 +22,8 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div>
{% if allow_download %}
<p><a class="report-download-link" href="#report_download" download="report.html">{% trans "Download report" %}</a></p>
{% endif %}
</div> </div>
\ No newline at end of file
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