Saving work in progress on annotations v2 with classification problems.
This commit is contained in:
@@ -73,19 +73,25 @@ class AnnotatableModule(XModule):
|
||||
def _set_problem(self, span, index, xmltree):
|
||||
""" Sets the associated problem. """
|
||||
|
||||
problem = span.find('problem')
|
||||
if problem is not None:
|
||||
problem_el = span.find('problem')
|
||||
if problem_el is not None:
|
||||
problem_id = str(index + 1)
|
||||
problem.set('problem_id', problem_id)
|
||||
problem_el.set('problem_id', problem_id)
|
||||
span.set('data-problem-id', problem_id)
|
||||
parsed_problem = self._parse_problem(problem)
|
||||
parsed_problem = self._parse_problem(problem_el)
|
||||
parsed_problem['discussion_id'] = span.get('data-discussion-id')
|
||||
if parsed_problem is not None:
|
||||
self.problems.append(parsed_problem)
|
||||
span.remove(problem_el)
|
||||
|
||||
def _parse_problem(self, problem):
|
||||
prompt_el = problem.find('prompt')
|
||||
answer_el = problem.find('answer')
|
||||
tags_el = problem.find('tags')
|
||||
def _parse_problem(self, problem_el):
|
||||
""" Returns the problem XML as a dict. """
|
||||
prompt_el = problem_el.find('prompt')
|
||||
answer_el = problem_el.find('answer')
|
||||
tags_el = problem_el.find('tags')
|
||||
|
||||
if any(v is None for v in (prompt_el, answer_el, tags_el)):
|
||||
return None
|
||||
|
||||
tags = []
|
||||
for tag_el in tags_el.iterchildren('tag'):
|
||||
@@ -96,20 +102,14 @@ class AnnotatableModule(XModule):
|
||||
'answer': tag_el.get('answer', 'n') == 'y'
|
||||
})
|
||||
|
||||
parsed = {
|
||||
'problem_id': problem.get('problem_id'),
|
||||
result = {
|
||||
'problem_id': problem_el.get('problem_id'),
|
||||
'prompt': prompt_el.text,
|
||||
'answer': answer_el.text,
|
||||
'tags': tags
|
||||
}
|
||||
|
||||
log.debug('parsed problem id = ' + parsed['problem_id'])
|
||||
log.debug("problem keys: " + ", ".join(parsed.keys()))
|
||||
log.debug('prompt = ' + parsed['prompt'])
|
||||
log.debug('answer = ' + parsed['answer'])
|
||||
log.debug('num tags = ' + str(len(parsed['tags'])))
|
||||
|
||||
return parsed
|
||||
return result
|
||||
|
||||
def _get_marker_color(self, span):
|
||||
""" Returns the name of the marker/highlight color for the span if it is valid, otherwise none."""
|
||||
@@ -151,16 +151,48 @@ class AnnotatableModule(XModule):
|
||||
|
||||
return etree.tostring(xmltree)
|
||||
|
||||
def _render_items(self):
|
||||
items = []
|
||||
|
||||
if self.render_as_problems:
|
||||
discussions = {}
|
||||
for child in self.get_display_items():
|
||||
discussions[child.discussion_id] = child.get_html()
|
||||
|
||||
for problem in self.problems:
|
||||
discussion = None
|
||||
discussion_id = problem['discussion_id']
|
||||
if discussion_id in discussions:
|
||||
discussion = {
|
||||
'discussion_id': discussion_id,
|
||||
'content': discussions[discussion_id]
|
||||
}
|
||||
items.append({
|
||||
'problem': problem,
|
||||
'discussion': discussion
|
||||
})
|
||||
else:
|
||||
for child in self.get_display_items():
|
||||
items.append({ 'discussion': {
|
||||
'discussion_id': child.discussion_id,
|
||||
'content': child.get_html()
|
||||
}})
|
||||
|
||||
return items
|
||||
|
||||
def get_html(self):
|
||||
""" Renders parameters to template. """
|
||||
|
||||
|
||||
html_content = self._render_content()
|
||||
items = self._render_items()
|
||||
|
||||
context = {
|
||||
'display_name': self.display_name,
|
||||
'problem_name': self.problem_name,
|
||||
'element_id': self.element_id,
|
||||
'html_content': self._render_content(),
|
||||
'has_problems': self.has_problems,
|
||||
'problems': self.problems
|
||||
'html_content': html_content,
|
||||
'render_as_problems': self.render_as_problems,
|
||||
'items': items
|
||||
}
|
||||
|
||||
return self.system.render_template('annotatable.html', context)
|
||||
@@ -175,7 +207,7 @@ class AnnotatableModule(XModule):
|
||||
self.element_id = self.location.html_id();
|
||||
self.content = self.definition['data']
|
||||
self.problem_type = xmltree.get('problem_type')
|
||||
self.has_problems = (self.problem_type == 'classification')
|
||||
self.render_as_problems = (self.problem_type == 'classification')
|
||||
self.problem_name = self._get_problem_name(self.problem_type)
|
||||
self.problems = []
|
||||
|
||||
@@ -184,3 +216,38 @@ class AnnotatableDescriptor(RawDescriptor):
|
||||
module_class = AnnotatableModule
|
||||
stores_state = True
|
||||
template_dir_name = "annotatable"
|
||||
|
||||
|
||||
@classmethod
|
||||
def definition_from_xml(cls, xml_object, system):
|
||||
children = []
|
||||
for child in xml_object.findall('discussion'):
|
||||
try:
|
||||
children.append(system.process_xml(etree.tostring(child, encoding='unicode')).location.url())
|
||||
xml_object.remove(child)
|
||||
except Exception as e:
|
||||
log.exception("Unable to load child when parsing Sequence. Continuing...")
|
||||
if system.error_tracker is not None:
|
||||
system.error_tracker("ERROR: " + str(e))
|
||||
continue
|
||||
return {
|
||||
'data': etree.tostring(xml_object, pretty_print=True, encoding='unicode'),
|
||||
'children': children
|
||||
}
|
||||
|
||||
def definition_to_xml(self, resource_fs):
|
||||
try:
|
||||
root = etree.fromstring(self.definition['data'])
|
||||
for child in self.get_children():
|
||||
root.append(etree.fromstring(child.export_to_xml(resource_fs)))
|
||||
return root
|
||||
except etree.XMLSyntaxError as err:
|
||||
# Can't recover here, so just add some info and
|
||||
# re-raise
|
||||
lines = self.definition['data'].split('\n')
|
||||
line, offset = err.position
|
||||
msg = ("Unable to create xml for problem {loc}. "
|
||||
"Context: '{context}'".format(
|
||||
context=lines[line - 1][offset - 40:offset + 40],
|
||||
loc=self.location))
|
||||
raise Exception, msg, sys.exc_info()[2]
|
||||
@@ -62,6 +62,9 @@
|
||||
|
||||
.annotatable-problems {
|
||||
margin: 25px 0 0 0;
|
||||
.annotatable-discussion {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.annotatable-problem {
|
||||
border: 1px solid #ccc;
|
||||
@@ -88,11 +91,15 @@
|
||||
margin: 1em 0;
|
||||
padding: 0;
|
||||
li {
|
||||
cursor: pointer;
|
||||
display: inline;
|
||||
padding: .5em;
|
||||
margin: 0 .5em 0 0;
|
||||
background-color: #ccc;
|
||||
border: 1px solid #000;
|
||||
&.selected {
|
||||
background-color: rgba(255,255,10,0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
.annotatable-problem-controls {
|
||||
|
||||
@@ -8,6 +8,9 @@ class @Annotatable
|
||||
replySelector: '.annotatable-reply'
|
||||
helpSelector: '.annotatable-help-icon'
|
||||
returnSelector: '.annotatable-return'
|
||||
problemSelector: '.annotatable-problem'
|
||||
problemSubmitSelector: '.annotatable-problem-submit'
|
||||
problemTagSelector: '.annotatable-problem-tags > li'
|
||||
|
||||
discussionXModuleSelector: '.xmodule_DiscussionModule'
|
||||
discussionSelector: '.discussion-module'
|
||||
@@ -25,7 +28,6 @@ class @Annotatable
|
||||
init: () ->
|
||||
@initEvents()
|
||||
@initTips()
|
||||
@initDiscussionReturnLinks()
|
||||
|
||||
initEvents: () ->
|
||||
@annotationsHidden = false
|
||||
@@ -33,6 +35,13 @@ class @Annotatable
|
||||
@$(@wrapperSelector).delegate @replySelector, 'click', @onClickReply
|
||||
$(@discussionXModuleSelector).delegate @returnSelector, 'click', @onClickReturn
|
||||
|
||||
problemSelector = @problemSelector
|
||||
@$(@problemSubmitSelector).bind 'click', (e) ->
|
||||
$(this).closest(problemSelector).next().show()
|
||||
|
||||
@$(@problemTagSelector).bind 'click', (e) ->
|
||||
$(this).toggleClass('selected')
|
||||
|
||||
initTips: () ->
|
||||
@savedTips = []
|
||||
@$(@spanSelector).each (index, el) => $(el).qtip(@getTipOptions el)
|
||||
@@ -45,10 +54,6 @@ class @Annotatable
|
||||
title: 'Annotated Reading'
|
||||
text: true # use title attribute of this element
|
||||
|
||||
initDiscussionReturnLinks: () ->
|
||||
$(@discussionXModuleSelector).find(@discussionSelector).each (index, el) =>
|
||||
$(el).before @createReturnLink(@getDiscussionId el)
|
||||
|
||||
getTipOptions: (el) ->
|
||||
content:
|
||||
title:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<%namespace name="annotatable" file="annotatable_problem.html"/>
|
||||
|
||||
<div class="annotatable-wrapper" id="${element_id}-wrapper">
|
||||
<div class="annotatable-header">
|
||||
% if display_name is not UNDEFINED and display_name is not None:
|
||||
@@ -12,32 +14,23 @@
|
||||
|
||||
<div class="annotatable-content">${html_content}</div>
|
||||
|
||||
% if has_problems:
|
||||
% if render_as_problems:
|
||||
<div class="annotatable-problems">
|
||||
% for problem in problems:
|
||||
<div class="annotatable-problem" data-problem-id="${problem['problem_id']}">
|
||||
<div class="annotatable-problem-header">
|
||||
Classification Exercise: <span class="annotatable-problem-index">(${loop.index + 1} / ${len(problems)}) </span>
|
||||
</div>
|
||||
<div class="annotatable-problem-body">
|
||||
<div class="annotatable-problem-prompt">${problem['prompt']}</div>
|
||||
<ul class="annotatable-problem-tags">
|
||||
% for tag in problem['tags']:
|
||||
<li>${tag['name']}</li>
|
||||
% endfor
|
||||
</ul>
|
||||
Explain the rationale for your tag selections:<br/>
|
||||
<textarea></textarea>
|
||||
<div class="annotatable-problem-controls">
|
||||
<button class="button annotatable-problem-save">Save</button>
|
||||
<button class="button annotatable-problem-submit">Submit</button>
|
||||
<a class="annotatable-problem-return" href="javascript:void(0);">Return to annotation</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="annotatable-problem-footer">
|
||||
</div>
|
||||
</div>
|
||||
% for item in items:
|
||||
${annotatable.render_problem(item['problem'],loop.index,len(items))}
|
||||
% if item['discussion']:
|
||||
<div class="annotatable-discussion">${item['discussion']['content']}</div>
|
||||
% endif
|
||||
% endfor
|
||||
</div>
|
||||
% else:
|
||||
<div class="annotatable-discussions">
|
||||
% for item in items:
|
||||
<div class="annotatable-discussion">
|
||||
<a class="annotatable-return" href="javascript:void(0);" data-discussion-id="${item['discussion']['discussion_id']}">Return to annotation</a>
|
||||
${item['discussion']['content']}
|
||||
</div>
|
||||
% endfor
|
||||
</div>
|
||||
% endif
|
||||
</div>
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
<div class="annotatable-problem" data-problem-id="${problem_id}">
|
||||
<%def name="render_problem(problem,index,total)">
|
||||
<div class="annotatable-problem" data-problem-id="${problem['problem_id']}">
|
||||
<div class="annotatable-problem-header">
|
||||
Classification Exercise:
|
||||
<span class="annotatable-problem-index">
|
||||
${problem_index} / ${total_problems}
|
||||
</span>
|
||||
Classification Exercise: <span class="annotatable-problem-index">(${index + 1} / ${total}) </span>
|
||||
</div>
|
||||
<div class="annotatable-problem-body">
|
||||
<div class="annotatable-problem-prompt">${problem_prompt}</div>
|
||||
<div class="annotatable-problem-prompt">${problem['prompt']}</div>
|
||||
<ul class="annotatable-problem-tags">
|
||||
% for tag in problem_tags:
|
||||
<li>${tag.name}</li>
|
||||
% for tag in problem['tags']:
|
||||
<li>${tag['name']}</li>
|
||||
% endfor
|
||||
</ul>
|
||||
Explain the rationale for your tag selections:<br/>
|
||||
<textarea></textarea>
|
||||
<div class="annotatable-problem-controls">
|
||||
<button class="button annotatable-problem-save">Save</button>
|
||||
<button class="button annotatable-problem-submit">Submit</button>
|
||||
<a class="annotatable-problem-return" href="javascript:void(0);" data-discussion-id="${problem['discussion_id']}">Return to annotation</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="annotatable-problem-footer">
|
||||
<button class="button">Save</button>
|
||||
<button class="button">Submit</button>
|
||||
<a href="javascript:void(0);">Return to annotation</a>
|
||||
</div>
|
||||
</div>
|
||||
</%def>
|
||||
Reference in New Issue
Block a user