Commit 0d101d8e by Braden MacDonald Committed by Jonathan Piacenti

Add CSV export to proof of concept

parent f57916c2
......@@ -23,7 +23,6 @@ Data Export: An XBlock for instructors to export student answers from a course.
All processing is done offline.
"""
import json
from .tasks import export_data as export_data_task
from xblock.core import XBlock
from xblock.fields import Scope, String, Dict
from xblock.fragment import Fragment
......@@ -71,6 +70,7 @@ class DataExportBlock(XBlock):
"""
If we're waiting for an export, see if it has finished, and if so, get the result.
"""
from .tasks import export_data as export_data_task # Import here since this is edX LMS specific
if self.active_export_task_id:
async_result = export_data_task.AsyncResult(self.active_export_task_id)
if async_result.ready():
......@@ -96,12 +96,24 @@ class DataExportBlock(XBlock):
html = loader.render_template('templates/html/data_export.html', {
'export_pending': bool(self.active_export_task_id),
'last_export_result': self.last_export_result,
'download_url': self.download_url_for_last_report,
})
fragment = Fragment(html)
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/data_export.js'))
fragment.initialize_js('DataExportBlock')
return fragment
@property
def download_url_for_last_report(self):
""" Get the URL for the last report, if any """
# Unfortunately this is a bit inefficient due to the ReportStore API
if not self.last_export_result or self.last_export_result["error"] is not None:
return None
from instructor_task.models import ReportStore
report_store = ReportStore.from_config()
course_key = self.scope_ids.usage_id.course_key
return dict(report_store.links_for(course_key)).get(self.last_export_result["report_filename"], None)
@XBlock.json_handler
def delete_export(self, request, suffix=''):
self._delete_export()
......@@ -114,6 +126,7 @@ class DataExportBlock(XBlock):
@XBlock.json_handler
def start_export(self, request, suffix=''):
""" Start a new asynchronous export """
from .tasks import export_data as export_data_task # Import here since this is edX LMS specific
# TODO: Verify instructor permissions
self._delete_export()
async_result = export_data_task.delay(unicode(self.scope_ids.usage_id), self.get_user_id())
......
"""
This file contains celery tasks for contentstore views
"""
import datetime
from celery.task import task
from celery.utils.log import get_task_logger
import datetime
from instructor_task.models import ReportStore
from opaque_keys.edx.keys import UsageKey
from xmodule.modulestore.django import modulestore
from .mcq import MCQBlock, RatingBlock
logger = get_task_logger(__name__)
......@@ -21,23 +23,47 @@ def export_data(source_block_id_str, user_id):
logger.debug("Beginning data export")
block_key = UsageKey.from_string(source_block_id_str)
block = modulestore().get_item(block_key)
src_block = modulestore().get_item(block_key)
course_key = src_block.scope_ids.usage_id.course_key
root = block
# Get the root block:
root = src_block
while root.parent:
root = root.get_parent()
course_name = root.display_name
# Build an ordered list of blocks to include in the export - each block is a column in the CSV file
blocks_to_include = []
def scan_for_blocks(block):
""" Recursively scan the course tree for blocks of interest """
if isinstance(block, (MCQBlock, RatingBlock)):
blocks_to_include.append(block)
elif block.has_children:
for child_id in block.children:
scan_for_blocks(block.runtime.get_block(child_id))
scan_for_blocks(root)
# Define the header rows of our CSV:
rows = []
rows.append(block.display_name_with_default for block in blocks_to_include)
rows.append(block.scope_ids.block_type for block in blocks_to_include)
rows.append(block.scope_ids.usage_id for block in blocks_to_include)
# Load the actual student submissions for each block in blocks_to_include.
# Note this requires one giant query per block (all student submissions for each block, one block at a time)
import time
time.sleep(5)
# Generate the CSV:
filename = u"pb-data-export-{}.csv".format(report_date.strftime("%Y-%m-%d-%H%M%S"))
report_store = ReportStore.from_config()
report_store.store_rows(course_key, filename, rows)
generation_time_s = (datetime.datetime.now() - report_date).total_seconds()
logger.debug("Done data export - took {} seconds".format(generation_time_s))
return {
"error": None,
"example": course_name,
"report_filename": filename,
"report_date": report_date.isoformat(),
"generation_time_s": generation_time_s,
}
......@@ -13,7 +13,7 @@
{% else %}
<p>Date: {{ last_export_result.report_date }}</p>
<p>Took {{ last_export_result.generation_time_s }} seconds to generate.</p>
<p>Example of block traversal and data access: {{ last_export_result.example }}</p>
<p>Download: <a href="{{download_url}}">{{ last_export_result.report_filename }}</a></p>
{% endif %}
<p><button class="delete-data-export">Delete export</button></p>
......
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