Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5be4fa3
Catch IndexError that gets thrown when user provides invalid root blo…
itsjeyd Jul 14, 2015
3fc5716
Implement client-server communication for pagination.
itsjeyd Jul 14, 2015
45bb18a
Turn "Root block ID" field into user-friendly dropdown.
itsjeyd Jul 15, 2015
9b2a4a1
Make sure spinner stays visible and result table stays hidden until
itsjeyd Jul 16, 2015
6fcbba2
Make dropdown work in workbench.
itsjeyd Jul 16, 2015
75994ad
Fix failing tests.
itsjeyd Jul 16, 2015
6e6f31c
Default to question ID (name) if question block does not have a
itsjeyd Jul 16, 2015
a2eb5c7
Refactor to create better separation of concerns in client-side code.
itsjeyd Jul 16, 2015
466b172
Add integration test for deleting export results.
itsjeyd Jul 16, 2015
5822565
Make sure not to override global beforeSend handler.
itsjeyd Jul 17, 2015
74e027c
Get rid of "Unable to find view u'studio_view' on block
itsjeyd Jul 17, 2015
a689bd2
Move code that computes block ID into separate method.
itsjeyd Jul 17, 2015
69b0a80
Take into account different key styles when computing block_type.
itsjeyd Jul 17, 2015
4cc0b2e
Move code that computes names of blocks to separate method and make sure
itsjeyd Jul 17, 2015
a856c87
Make sure dropdown excludes all choice fields (not just children of M…
itsjeyd Jul 17, 2015
418c55f
Make sure long block names can't break layout of export options.
itsjeyd Jul 17, 2015
435382f
Handle cases where "Question title" is set but "question" isn't.
itsjeyd Jul 17, 2015
51d02cf
Look up "Long Answer" submissions by question ID instead of block ID.
itsjeyd Jul 20, 2015
bb2ea26
Store question ID in "Question" column if "question" attribute is not
itsjeyd Jul 21, 2015
e0fbd14
In dropdown listing blocks, show question ID (name) instead of block ID
itsjeyd Jul 21, 2015
c6ab809
Address review comments.
itsjeyd Jul 21, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 111 additions & 6 deletions problem_builder/instructor_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,17 @@
All processing is done offline.
"""
import json
from django.core.paginator import Paginator
from xblock.core import XBlock
from xblock.exceptions import JsonHandlerError
from xblock.fields import Scope, String, Dict
from xblock.fields import Scope, String, Dict, List
from xblock.fragment import Fragment
from xblockutils.resources import ResourceLoader

loader = ResourceLoader(__name__)

PAGE_SIZE = 15


# Make '_' a no-op so we can scrape strings
def _(text):
Expand Down Expand Up @@ -63,6 +66,12 @@ class InstructorToolBlock(XBlock):
default=None,
scope=Scope.user_state,
)
display_data = List(
# The list of results associated with the most recent successful export.
# Stored separately to avoid the overhead of sending it to the client.
default=None,
scope=Scope.user_state,
)
has_author_view = True

@property
Expand All @@ -75,6 +84,11 @@ def author_view(self, context=None):
# different celery queues; our task listener is waiting for tasks on the LMS queue)
return Fragment(u'<p>Instructor Tool Block</p><p>This block only works from the LMS.</p>')

def studio_view(self, context=None):
""" View for editing Instructor Tool block in Studio. """
# Display friendly message explaining that the block is not editable.
return Fragment(u'<p>This is a preconfigured block. It is not editable.</p>')

def check_pending_export(self):
"""
If we're waiting for an export, see if it has finished, and if so, get the result.
Expand All @@ -90,11 +104,26 @@ def _save_result(self, task_result):
self.active_export_task_id = ''
if task_result.successful():
if isinstance(task_result.result, dict) and not task_result.result.get('error'):
self.display_data = task_result.result['display_data']
del task_result.result['display_data']
self.last_export_result = task_result.result
else:
self.last_export_result = {'error': u'Unexpected result: {}'.format(repr(task_result.result))}
self.display_data = None
else:
self.last_export_result = {'error': unicode(task_result.result)}
self.display_data = None

@XBlock.json_handler
def get_result_page(self, data, suffix=''):
""" Return requested page of `last_export_result`. """
paginator = Paginator(self.display_data, PAGE_SIZE)
page = data.get('page', None)
return {
'display_data': paginator.page(page).object_list,
'num_results': len(self.display_data),
'page_size': PAGE_SIZE
}

def student_view(self, context=None):
""" Normal View """
Expand All @@ -105,9 +134,87 @@ def student_view(self, context=None):
_('Rating Question'): 'RatingBlock',
_('Long Answer'): 'AnswerBlock',
}
eligible_block_types = ('pb-mcq', 'pb-rating', 'pb-answer')
flat_block_tree = []

def get_block_id(block):
"""
Return ID of `block`, taking into account needs of both LMS/CMS and workbench runtimes.
"""
usage_id = block.scope_ids.usage_id
# Try accessing block ID. If usage_id does not have it, return usage_id itself
return unicode(getattr(usage_id, 'block_id', usage_id))

def get_block_name(block):
"""
Return name of `block`.

Try attributes in the following order:
- block.question
- block.name (fallback for old courses)
- block.display_name
- block ID
"""
for attribute in ('question', 'name', 'display_name'):
if getattr(block, attribute, None):
return getattr(block, attribute, None)
return get_block_id(block)

def get_block_type(block):
"""
Return type of `block`, taking into account different key styles that might be in use.
"""
try:
block_type = block.runtime.id_reader.get_block_type(block.scope_ids.def_id)
except AttributeError:
block_type = block.runtime.id_reader.get_block_type(block.scope_ids.usage_id)
return block_type

def build_tree(block, ancestors):
"""
Build up a tree of information about the XBlocks descending from root_block
"""
block_id = get_block_id(block)
block_name = get_block_name(block)
block_type = get_block_type(block)
if block_type != 'pb-choice':
eligible = block_type in eligible_block_types
if eligible:
# If this block is a question whose answers we can export,
# we mark all of its ancestors as exportable too
if ancestors and not ancestors[-1]["eligible"]:
for ancestor in ancestors:
ancestor["eligible"] = True

new_entry = {
"depth": len(ancestors),
"id": block_id,
"name": block_name,
"eligible": eligible,
}
flat_block_tree.append(new_entry)
if block.has_children and not getattr(block, "has_dynamic_children", lambda: False)():
for child_id in block.children:
build_tree(block.runtime.get_block(child_id), ancestors=(ancestors + [new_entry]))

root_block = self
while root_block.parent:
root_block = root_block.get_parent()
root_block_id = get_block_id(root_block)
root_entry = {
"depth": 0,
"id": root_block_id,
"name": "All",
}
flat_block_tree.append(root_entry)

for child_id in root_block.children:
child_block = root_block.runtime.get_block(child_id)
build_tree(child_block, [root_entry])

html = loader.render_template(
'templates/html/instructor_tool.html',
{'block_choices': block_choices}
{'block_choices': block_choices, 'block_tree': flat_block_tree}
)
fragment = Fragment(html)
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/instructor_tool.css'))
Expand Down Expand Up @@ -144,6 +251,7 @@ def raise_error(self, code, message):
self.last_export_result = {
'error': message,
}
self.display_data = None
raise JsonHandlerError(code, message)

@XBlock.json_handler
Expand All @@ -157,6 +265,7 @@ def delete_export(self, data, suffix=''):

def _delete_export(self):
self.last_export_result = None
self.display_data = None
self.active_export_task_id = ''

@XBlock.json_handler
Expand Down Expand Up @@ -187,9 +296,6 @@ def start_export(self, data, suffix=''):
root_block_id = self.scope_ids.usage_id
# Block ID not in workbench runtime.
root_block_id = unicode(getattr(root_block_id, 'block_id', root_block_id))
get_root = True
else:
get_root = False

# Launch task
from .tasks import export_data as export_data_task # Import here since this is edX LMS specific
Expand All @@ -203,7 +309,6 @@ def start_export(self, data, suffix=''):
block_types,
user_id,
match_string,
get_root=get_root
)
if async_result.ready():
# In development mode, the task may have executed synchronously.
Expand Down
8 changes: 7 additions & 1 deletion problem_builder/public/css/instructor_tool.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
display: table-cell;
padding-left: 1em;
}
.data-export-field-container {
width: 43%;
}
.data-export-options .data-export-actions {
max-width: 10%;
}
.data-export-field {
margin-top: .5em;
margin-bottom: .5em;
Expand All @@ -34,7 +40,7 @@
vertical-align: middle;
}
.data-export-field input, .data-export-field select {
max-width: 60%;
width: 55%;
float: right;
}
.data-export-results, .data-export-download, .data-export-cancel, .data-export-delete {
Expand Down
Loading