+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py
index 0441357427..e6c8fe577d 100644
--- a/common/lib/xmodule/setup.py
+++ b/common/lib/xmodule/setup.py
@@ -28,6 +28,7 @@ setup(
"problem = xmodule.capa_module:CapaDescriptor",
"problemset = xmodule.seq_module:SequenceDescriptor",
"section = xmodule.backcompat_module:SemanticSectionDescriptor",
+ "selfassessment = xmodule.self_assessment_module:SelfAssessmentModule"
"sequential = xmodule.seq_module:SequenceDescriptor",
"slides = xmodule.backcompat_module:TranslateCustomTagDescriptor",
"vertical = xmodule.vertical_module:VerticalDescriptor",
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
new file mode 100644
index 0000000000..c0dd835208
--- /dev/null
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -0,0 +1,174 @@
+import copy
+from fs.errors import ResourceNotFoundError
+import logging
+import os
+import sys
+from lxml import etree
+from lxml.html import rewrite_links
+from path import path
+
+from .x_module import XModule
+from pkg_resources import resource_string
+from .xml_module import XmlDescriptor, name_to_pathname
+from .editing_module import EditingDescriptor
+from .stringify import stringify_children
+from .html_checker import check_html
+from xmodule.modulestore import Location
+
+from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent
+
+log = logging.getLogger("mitx.courseware")
+
+
+class SelfAssessmentModule(XModule):
+ js = {'coffee': [resource_string(__name__, 'js/src/javascript_loader.coffee'),
+ resource_string(__name__, 'js/src/collapsible.coffee'),
+ resource_string(__name__, 'js/src/html/display.coffee')
+ ]
+ }
+ js_module_name = "SelfAssessmentModule"
+
+ def get_html(self):
+ # cdodge: perform link substitutions for any references to course static content (e.g. images)
+ return rewrite_links(self.html, self.rewrite_content_links)
+
+ def __init__(self, system, location, definition, descriptor,
+ instance_state=None, shared_state=None, **kwargs):
+ XModule.__init__(self, system, location, definition, descriptor,
+ instance_state, shared_state, **kwargs)
+ self.html = self.definition['data']
+
+
+
+class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
+ """
+ Module for putting raw html in a course
+ """
+ mako_template = "widgets/html-edit.html"
+ module_class = HtmlModule
+ filename_extension = "xml"
+ template_dir_name = "selfassessment"
+
+ js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]}
+ js_module_name = "HTMLEditingDescriptor"
+
+ # VS[compat] TODO (cpennington): Delete this method once all fall 2012 course
+ # are being edited in the cms
+ @classmethod
+ def backcompat_paths(cls, path):
+ if path.endswith('.html.xml'):
+ path = path[:-9] + '.html' # backcompat--look for html instead of xml
+ if path.endswith('.html.html'):
+ path = path[:-5] # some people like to include .html in filenames..
+ candidates = []
+ while os.sep in path:
+ candidates.append(path)
+ _, _, path = path.partition(os.sep)
+
+ # also look for .html versions instead of .xml
+ nc = []
+ for candidate in candidates:
+ if candidate.endswith('.xml'):
+ nc.append(candidate[:-4] + '.html')
+ return candidates + nc
+
+ # NOTE: html descriptors are special. We do not want to parse and
+ # export them ourselves, because that can break things (e.g. lxml
+ # adds body tags when it exports, but they should just be html
+ # snippets that will be included in the middle of pages.
+
+ @classmethod
+ def load_definition(cls, xml_object, system, location):
+ '''Load a descriptor from the specified xml_object:
+
+ If there is a filename attribute, load it as a string, and
+ log a warning if it is not parseable by etree.HTMLParser.
+
+ If there is not a filename attribute, the definition is the body
+ of the xml_object, without the root tag (do not want in the
+ middle of a page)
+ '''
+ filename = xml_object.get('filename')
+ if filename is None:
+ definition_xml = copy.deepcopy(xml_object)
+ cls.clean_metadata_from_xml(definition_xml)
+ return {'data': stringify_children(definition_xml)}
+ else:
+ # html is special. cls.filename_extension is 'xml', but
+ # if 'filename' is in the definition, that means to load
+ # from .html
+ # 'filename' in html pointers is a relative path
+ # (not same as 'html/blah.html' when the pointer is in a directory itself)
+ pointer_path = "{category}/{url_path}".format(category='html',
+ url_path=name_to_pathname(location.name))
+ base = path(pointer_path).dirname()
+ #log.debug("base = {0}, base.dirname={1}, filename={2}".format(base, base.dirname(), filename))
+ filepath = "{base}/{name}.html".format(base=base, name=filename)
+ #log.debug("looking for html file for {0} at {1}".format(location, filepath))
+
+
+
+ # VS[compat]
+ # TODO (cpennington): If the file doesn't exist at the right path,
+ # give the class a chance to fix it up. The file will be written out
+ # again in the correct format. This should go away once the CMS is
+ # online and has imported all current (fall 2012) courses from xml
+ if not system.resources_fs.exists(filepath):
+ candidates = cls.backcompat_paths(filepath)
+ #log.debug("candidates = {0}".format(candidates))
+ for candidate in candidates:
+ if system.resources_fs.exists(candidate):
+ filepath = candidate
+ break
+
+ try:
+ with system.resources_fs.open(filepath) as file:
+ html = file.read()
+ # Log a warning if we can't parse the file, but don't error
+ if not check_html(html):
+ msg = "Couldn't parse html in {0}.".format(filepath)
+ log.warning(msg)
+ system.error_tracker("Warning: " + msg)
+
+ definition = {'data': html}
+
+ # TODO (ichuang): remove this after migration
+ # for Fall 2012 LMS migration: keep filename (and unmangled filename)
+ definition['filename'] = [ filepath, filename ]
+
+ return definition
+
+ except (ResourceNotFoundError) as err:
+ msg = 'Unable to load file contents at path {0}: {1} '.format(
+ filepath, err)
+ # add more info and re-raise
+ raise Exception(msg), None, sys.exc_info()[2]
+
+ # TODO (vshnayder): make export put things in the right places.
+
+ def definition_to_xml(self, resource_fs):
+ '''If the contents are valid xml, write them to filename.xml. Otherwise,
+ write just to filename.xml, and the html
+ string to filename.html.
+ '''
+ try:
+ return etree.fromstring(self.definition['data'])
+ except etree.XMLSyntaxError:
+ pass
+
+ # Not proper format. Write html to file, return an empty tag
+ pathname = name_to_pathname(self.url_name)
+ pathdir = path(pathname).dirname()
+ filepath = u'{category}/{pathname}.html'.format(category=self.category,
+ pathname=pathname)
+
+ resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True)
+ with resource_fs.open(filepath, 'w') as file:
+ file.write(self.definition['data'])
+
+ # write out the relative name
+ relname = path(pathname).basename()
+
+ elt = etree.Element('html')
+ elt.set("filename", relname)
+ return elt
From 62d1f1fe3ee54423012eecc89a050d353c5764f5 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Tue, 30 Oct 2012 20:14:55 -0400
Subject: [PATCH 002/121] update gitignore for pycharm
---
.gitignore | 1 +
.idea/.name | 1 -
.idea/encodings.xml | 5 -
.../inspectionProfiles/profiles_settings.xml | 7 -
.idea/misc.xml | 34 --
.idea/mitx.iml | 9 -
.idea/modules.xml | 9 -
.idea/scopes/scope_settings.xml | 5 -
.idea/vcs.xml | 7 -
.idea/workspace.xml | 394 ------------------
lms/static/admin/css/ie.css | 63 ---
test_root/log/.git-keep | 0
12 files changed, 1 insertion(+), 534 deletions(-)
delete mode 100644 .idea/.name
delete mode 100644 .idea/encodings.xml
delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml
delete mode 100644 .idea/misc.xml
delete mode 100644 .idea/mitx.iml
delete mode 100644 .idea/modules.xml
delete mode 100644 .idea/scopes/scope_settings.xml
delete mode 100644 .idea/vcs.xml
delete mode 100644 .idea/workspace.xml
delete mode 100644 lms/static/admin/css/ie.css
delete mode 100644 test_root/log/.git-keep
diff --git a/.gitignore b/.gitignore
index 81d9a57d3c..f24bdb1bfc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,4 @@ cms/static/sass/*.css
lms/lib/comment_client/python
nosetests.xml
cover_html/
+.idea/
diff --git a/.idea/.name b/.idea/.name
deleted file mode 100644
index 36d4bf6d51..0000000000
--- a/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-mitx
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
deleted file mode 100644
index e206d70d85..0000000000
--- a/.idea/encodings.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index c60c33bb47..0000000000
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 715d0f8f2d..0000000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- $APPLICATION_HOME_DIR$/lib/pycharm.jar!/resources/html5-schema/html5.rnc
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/lms/static/admin/css/ie.css b/lms/static/admin/css/ie.css
deleted file mode 100644
index fd00f7f204..0000000000
--- a/lms/static/admin/css/ie.css
+++ /dev/null
@@ -1,63 +0,0 @@
-/* IE 6 & 7 */
-
-/* Proper fixed width for dashboard in IE6 */
-
-.dashboard #content {
- *width: 768px;
-}
-
-.dashboard #content-main {
- *width: 535px;
-}
-
-/* IE 6 ONLY */
-
-/* Keep header from flowing off the page */
-
-#container {
- _position: static;
-}
-
-/* Put the right sidebars back on the page */
-
-.colMS #content-related {
- _margin-right: 0;
- _margin-left: 10px;
- _position: static;
-}
-
-/* Put the left sidebars back on the page */
-
-.colSM #content-related {
- _margin-right: 10px;
- _margin-left: -115px;
- _position: static;
-}
-
-.form-row {
- _height: 1%;
-}
-
-/* Fix right margin for changelist filters in IE6 */
-
-#changelist-filter ul {
- _margin-right: -10px;
-}
-
-/* IE ignores min-height, but treats height as if it were min-height */
-
-.change-list .filtered {
- _height: 400px;
-}
-
-/* IE doesn't know alpha transparency in PNGs */
-
-.inline-deletelink {
- background: transparent url(../img/inline-delete-8bit.png) no-repeat;
-}
-
-/* IE7 doesn't support inline-block */
-.change-list ul.toplinks li {
- zoom: 1;
- *display: inline;
-}
\ No newline at end of file
diff --git a/test_root/log/.git-keep b/test_root/log/.git-keep
deleted file mode 100644
index e69de29bb2..0000000000
From 5586139594b0b4295aba32be00abef95cfaea52c Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Tue, 30 Oct 2012 20:36:45 -0400
Subject: [PATCH 003/121] bugfix to self assessment
---
common/lib/xmodule/setup.py | 2 +-
common/lib/xmodule/xmodule/self_assessment_module.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py
index e6c8fe577d..d3889bc388 100644
--- a/common/lib/xmodule/setup.py
+++ b/common/lib/xmodule/setup.py
@@ -28,7 +28,7 @@ setup(
"problem = xmodule.capa_module:CapaDescriptor",
"problemset = xmodule.seq_module:SequenceDescriptor",
"section = xmodule.backcompat_module:SemanticSectionDescriptor",
- "selfassessment = xmodule.self_assessment_module:SelfAssessmentModule"
+ "selfassessment = xmodule.self_assessment_module:SelfAssessmentDescriptor",
"sequential = xmodule.seq_module:SequenceDescriptor",
"slides = xmodule.backcompat_module:TranslateCustomTagDescriptor",
"vertical = xmodule.vertical_module:VerticalDescriptor",
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index c0dd835208..b8c2e85ef0 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -45,7 +45,7 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
Module for putting raw html in a course
"""
mako_template = "widgets/html-edit.html"
- module_class = HtmlModule
+ module_class = SelfAssessmentModule
filename_extension = "xml"
template_dir_name = "selfassessment"
From 7fc54dc717e3cdac12059a766e5d9bbe6e94ea0f Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Tue, 30 Oct 2012 20:38:33 -0400
Subject: [PATCH 004/121] fix docstring
---
common/lib/xmodule/xmodule/self_assessment_module.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index b8c2e85ef0..f1b8353c75 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -42,7 +42,7 @@ class SelfAssessmentModule(XModule):
class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
"""
- Module for putting raw html in a course
+ Module for putting self assessment questions into a course
"""
mako_template = "widgets/html-edit.html"
module_class = SelfAssessmentModule
From 0f5af40bf5fa490e958c48207e5ec46493b93bae Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Wed, 31 Oct 2012 13:06:05 -0400
Subject: [PATCH 005/121] add self assessment
---
.../js/src/selfassessment/display.coffee | 303 ++++++++++++++++++
.../xmodule/xmodule/self_assessment_module.py | 29 +-
2 files changed, 331 insertions(+), 1 deletion(-)
create mode 100644 common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
new file mode 100644
index 0000000000..5d5e3a8eb8
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
@@ -0,0 +1,303 @@
+class @Problem
+
+ constructor: (element) ->
+ @el = $(element).find('.problems-wrapper')
+ @id = @el.data('problem-id')
+ @element_id = @el.attr('id')
+ @url = @el.data('url')
+ @render()
+
+ $: (selector) ->
+ $(selector, @el)
+
+ bind: =>
+ problem_prefix = @element_id.replace(/problem_/,'')
+ @inputs = @$("[id^=input_#{problem_prefix}_]")
+
+ @$('section.action input:button').click @refreshAnswers
+ @$('section.action input.check').click @check_fd
+ #@$('section.action input.check').click @check
+ @$('section.action input.show').click @show
+ @$('section.action input.save').click @save
+
+ render: (content) ->
+ if content
+ @el.html(content)
+ JavascriptLoader.executeModuleScripts @el, () =>
+ @setupInputTypes()
+ @bind()
+ else
+ $.postWithPrefix "#{@url}/problem_get", (response) =>
+ @el.html(response.html)
+ JavascriptLoader.executeModuleScripts @el, () =>
+ @setupInputTypes()
+ @bind()
+
+
+ # TODO add hooks for problem types here by inspecting response.html and doing
+ # stuff if a div w a class is found
+
+ setupInputTypes: =>
+ @inputtypeDisplays = {}
+ @el.find(".capa_inputtype").each (index, inputtype) =>
+ classes = $(inputtype).attr('class').split(' ')
+ id = $(inputtype).attr('id')
+ for cls in classes
+ setupMethod = @inputtypeSetupMethods[cls]
+ if setupMethod?
+ @inputtypeDisplays[id] = setupMethod(inputtype)
+
+
+ ###
+ # 'check_fd' uses FormData to allow file submissions in the 'problem_check' dispatch,
+ # in addition to simple querystring-based answers
+ #
+ # NOTE: The dispatch 'problem_check' is being singled out for the use of FormData;
+ # maybe preferable to consolidate all dispatches to use FormData
+ ###
+ check_fd: =>
+ Logger.log 'problem_check', @answers
+
+ # If there are no file inputs in the problem, we can fall back on @check
+ if $('input:file').length == 0
+ @check()
+ return
+
+ if not window.FormData
+ alert "Submission aborted! Sorry, your browser does not support file uploads. If you can, please use Chrome or Safari which have been verified to support file uploads."
+ return
+
+ fd = new FormData()
+
+ # Sanity checks on submission
+ max_filesize = 4*1000*1000 # 4 MB
+ file_too_large = false
+ file_not_selected = false
+ required_files_not_submitted = false
+ unallowed_file_submitted = false
+
+ errors = []
+
+ @inputs.each (index, element) ->
+ if element.type is 'file'
+ required_files = $(element).data("required_files")
+ allowed_files = $(element).data("allowed_files")
+ for file in element.files
+ if allowed_files.length != 0 and file.name not in allowed_files
+ unallowed_file_submitted = true
+ errors.push "You submitted #{file.name}; only #{allowed_files} are allowed."
+ if file.name in required_files
+ required_files.splice(required_files.indexOf(file.name), 1)
+ if file.size > max_filesize
+ file_too_large = true
+ errors.push 'Your file "' + file.name '" is too large (max size: ' + max_filesize/(1000*1000) + ' MB)'
+ fd.append(element.id, file)
+ if element.files.length == 0
+ file_not_selected = true
+ fd.append(element.id, '') # In case we want to allow submissions with no file
+ if required_files.length != 0
+ required_files_not_submitted = true
+ errors.push "You did not submit the required files: #{required_files}."
+ else
+ fd.append(element.id, element.value)
+
+
+ if file_not_selected
+ errors.push 'You did not select any files to submit'
+
+ error_html = '
\n'
+ for error in errors
+ error_html += '
' + error + '
\n'
+ error_html += '
'
+ @gentle_alert error_html
+
+ abort_submission = file_too_large or file_not_selected or unallowed_file_submitted or required_files_not_submitted
+
+ settings =
+ type: "POST"
+ data: fd
+ processData: false
+ contentType: false
+ success: (response) =>
+ switch response.success
+ when 'incorrect', 'correct'
+ @render(response.contents)
+ @updateProgress response
+ else
+ @gentle_alert response.success
+
+ if not abort_submission
+ $.ajaxWithPrefix("#{@url}/problem_check", settings)
+
+ check: =>
+ Logger.log 'problem_check', @answers
+ $.postWithPrefix "#{@url}/problem_check", @answers, (response) =>
+ switch response.success
+ when 'incorrect', 'correct'
+ @render(response.contents)
+ @updateProgress response
+ if @el.hasClass 'showed'
+ @el.removeClass 'showed'
+ else
+ @gentle_alert response.success
+
+ reset: =>
+ Logger.log 'problem_reset', @answers
+ $.postWithPrefix "#{@url}/problem_reset", id: @id, (response) =>
+ @render(response.html)
+ @updateProgress response
+
+ # TODO this needs modification to deal with javascript responses; perhaps we
+ # need something where responsetypes can define their own behavior when show
+ # is called.
+ show: =>
+ if !@el.hasClass 'showed'
+ Logger.log 'problem_show', problem: @id
+ $.postWithPrefix "#{@url}/problem_show", (response) =>
+ answers = response.answers
+ $.each answers, (key, value) =>
+ if $.isArray(value)
+ for choice in value
+ @$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true'
+ else
+ answer = @$("#answer_#{key}, #solution_#{key}")
+ answer.html(value)
+ Collapsible.setCollapsibles(answer)
+
+ # TODO remove the above once everything is extracted into its own
+ # inputtype functions.
+
+ @el.find(".capa_inputtype").each (index, inputtype) =>
+ classes = $(inputtype).attr('class').split(' ')
+ for cls in classes
+ display = @inputtypeDisplays[$(inputtype).attr('id')]
+ showMethod = @inputtypeShowAnswerMethods[cls]
+ showMethod(inputtype, display, answers) if showMethod?
+
+ @el.find('.problem > div').each (index, element) =>
+ MathJax.Hub.Queue ["Typeset", MathJax.Hub, element]
+
+ @$('.show').val 'Hide Answer'
+ @el.addClass 'showed'
+ @updateProgress response
+ else
+ @$('[id^=answer_], [id^=solution_]').text ''
+ @$('[correct_answer]').attr correct_answer: null
+ @el.removeClass 'showed'
+ @$('.show').val 'Show Answer'
+
+ @el.find(".capa_inputtype").each (index, inputtype) =>
+ display = @inputtypeDisplays[$(inputtype).attr('id')]
+ classes = $(inputtype).attr('class').split(' ')
+ for cls in classes
+ hideMethod = @inputtypeHideAnswerMethods[cls]
+ hideMethod(inputtype, display) if hideMethod?
+
+ gentle_alert: (msg) =>
+ if @el.find('.capa_alert').length
+ @el.find('.capa_alert').remove()
+ alert_elem = "
" + msg + "
"
+ @el.find('.action').after(alert_elem)
+ @el.find('.capa_alert').css(opacity: 0).animate(opacity: 1, 700)
+
+ save: =>
+ Logger.log 'problem_save', @answers
+ $.postWithPrefix "#{@url}/problem_save", @answers, (response) =>
+ if response.success
+ saveMessage = "Your answers have been saved but not graded. Hit 'Check' to grade them."
+ @gentle_alert saveMessage
+ @updateProgress response
+
+ refreshMath: (event, element) =>
+ element = event.target unless element
+ elid = element.id.replace(/^input_/,'')
+ target = "display_" + elid
+
+ # MathJax preprocessor is loaded by 'setupInputTypes'
+ preprocessor_tag = "inputtype_" + elid
+ mathjax_preprocessor = @inputtypeDisplays[preprocessor_tag]
+
+ if jax = MathJax.Hub.getAllJax(target)[0]
+ eqn = $(element).val()
+ if mathjax_preprocessor
+ eqn = mathjax_preprocessor(eqn)
+ MathJax.Hub.Queue(['Text', jax, eqn], [@updateMathML, jax, element])
+
+ return # Explicit return for CoffeeScript
+
+ updateMathML: (jax, element) =>
+ try
+ $("##{element.id}_dynamath").val(jax.root.toMathML '')
+ catch exception
+ throw exception unless exception.restart
+ MathJax.Callback.After [@refreshMath, jax], exception.restart
+
+ refreshAnswers: =>
+ @$('input.schematic').each (index, element) ->
+ element.schematic.update_value()
+ @$(".CodeMirror").each (index, element) ->
+ element.CodeMirror.save() if element.CodeMirror.save
+ @answers = @inputs.serialize()
+
+ inputtypeSetupMethods:
+
+ 'text-input-dynamath': (element) =>
+ ###
+ Return: function (eqn) -> eqn that preprocesses the user formula input before
+ it is fed into MathJax. Return 'false' if no preprocessor specified
+ ###
+ data = $(element).find('.text-input-dynamath_data')
+
+ preprocessorClassName = data.data('preprocessor')
+ preprocessorClass = window[preprocessorClassName]
+ if not preprocessorClass?
+ return false
+ else
+ preprocessor = new preprocessorClass()
+ return preprocessor.fn
+
+ javascriptinput: (element) =>
+
+ data = $(element).find(".javascriptinput_data")
+
+ params = data.data("params")
+ submission = data.data("submission")
+ evaluation = data.data("evaluation")
+ problemState = data.data("problem_state")
+ displayClass = window[data.data('display_class')]
+
+ if evaluation == ''
+ evaluation = null
+
+ container = $(element).find(".javascriptinput_container")
+ submissionField = $(element).find(".javascriptinput_input")
+
+ display = new displayClass(problemState, submission, evaluation, container, submissionField, params)
+ display.render()
+
+ return display
+
+ inputtypeShowAnswerMethods:
+ choicegroup: (element, display, answers) =>
+ element = $(element)
+
+ element.find('input').attr('disabled', 'disabled')
+
+ input_id = element.attr('id').replace(/inputtype_/,'')
+ answer = answers[input_id]
+ for choice in answer
+ element.find("label[for='input_#{input_id}_#{choice}']").addClass 'choicegroup_correct'
+
+ javascriptinput: (element, display, answers) =>
+ answer_id = $(element).attr('id').split("_")[1...].join("_")
+ answer = JSON.parse(answers[answer_id])
+ display.showAnswer(answer)
+
+ inputtypeHideAnswerMethods:
+ choicegroup: (element, display) =>
+ element = $(element)
+ element.find('input').attr('disabled', null)
+ element.find('label').removeClass('choicegroup_correct')
+
+ javascriptinput: (element, display) =>
+ display.hideAnswer()
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index f1b8353c75..17aa3832d8 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -23,7 +23,7 @@ log = logging.getLogger("mitx.courseware")
class SelfAssessmentModule(XModule):
js = {'coffee': [resource_string(__name__, 'js/src/javascript_loader.coffee'),
resource_string(__name__, 'js/src/collapsible.coffee'),
- resource_string(__name__, 'js/src/html/display.coffee')
+ resource_string(__name__, 'js/src/selfassessment/display.coffee')
]
}
js_module_name = "SelfAssessmentModule"
@@ -38,6 +38,33 @@ class SelfAssessmentModule(XModule):
instance_state, shared_state, **kwargs)
self.html = self.definition['data']
+ def handle_ajax(self, dispatch, get):
+ '''
+ This is called by courseware.module_render, to handle an AJAX call.
+ "get" is request.POST.
+
+ Returns a json dictionary:
+ { 'progress_changed' : True/False,
+ 'progress' : 'none'/'in_progress'/'done',
+ }
+ '''
+ handlers = {
+ 'problem_get': self.get_problem,
+ 'problem_check': self.check_problem,
+ 'problem_save': self.save_problem,
+ }
+
+ if dispatch not in handlers:
+ return 'Error'
+
+ before = self.get_progress()
+ d = handlers[dispatch](get)
+ after = self.get_progress()
+ d.update({
+ 'progress_changed': after != before,
+ 'progress_status': Progress.to_js_status_str(after),
+ })
+ return json.dumps(d, cls=ComplexEncoder)
class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
From 2cb38a2c0fd81384ab293f8e896756526c4b6fb1 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Wed, 31 Oct 2012 15:37:43 -0400
Subject: [PATCH 006/121] working on new xmodule
---
.../0-4455953e190a120e56a33841e5c04a9b.coffee | 9 +
.../0-792b1e29b66e460f0798bdd940c30ad8.coffee | 10 +
.../0-79a64f2010d8b4cb8d0f0d6912c70c12.coffee | 10 +
.../0-c0f10bbfa80be09f66bef86df2e1152a.coffee | 11 +
.../0-fdfb7ccca8edfb703fc7a5f124a67f24.coffee | 9 +
.../_0-3d2f9902e9ecf2dac554cd9b9e661a89.scss | 5 +
.../sass/descriptor/_module-styles.scss | 1 +
.../js/src/selfassessment/display.coffee | 85 +--
.../xmodule/xmodule/self_assessment_module.py | 516 +++++++++++++-----
9 files changed, 441 insertions(+), 215 deletions(-)
create mode 100644 cms/static/coffee/descriptor/0-4455953e190a120e56a33841e5c04a9b.coffee
create mode 100644 cms/static/coffee/descriptor/0-792b1e29b66e460f0798bdd940c30ad8.coffee
create mode 100644 cms/static/coffee/descriptor/0-79a64f2010d8b4cb8d0f0d6912c70c12.coffee
create mode 100644 cms/static/coffee/descriptor/0-c0f10bbfa80be09f66bef86df2e1152a.coffee
create mode 100644 cms/static/coffee/descriptor/0-fdfb7ccca8edfb703fc7a5f124a67f24.coffee
create mode 100644 cms/static/sass/descriptor/_0-3d2f9902e9ecf2dac554cd9b9e661a89.scss
create mode 100644 cms/static/sass/descriptor/_module-styles.scss
diff --git a/cms/static/coffee/descriptor/0-4455953e190a120e56a33841e5c04a9b.coffee b/cms/static/coffee/descriptor/0-4455953e190a120e56a33841e5c04a9b.coffee
new file mode 100644
index 0000000000..33942bc97d
--- /dev/null
+++ b/cms/static/coffee/descriptor/0-4455953e190a120e56a33841e5c04a9b.coffee
@@ -0,0 +1,9 @@
+class @SequenceDescriptor extends XModule.Descriptor
+ constructor: (@element) ->
+ @$tabs = $(@element).find("#sequence-list")
+ @$tabs.sortable(
+ update: (event, ui) => @update()
+ )
+
+ save: ->
+ children: $('#sequence-list li a', @element).map((idx, el) -> $(el).data('id')).toArray()
diff --git a/cms/static/coffee/descriptor/0-792b1e29b66e460f0798bdd940c30ad8.coffee b/cms/static/coffee/descriptor/0-792b1e29b66e460f0798bdd940c30ad8.coffee
new file mode 100644
index 0000000000..7c3c09cefc
--- /dev/null
+++ b/cms/static/coffee/descriptor/0-792b1e29b66e460f0798bdd940c30ad8.coffee
@@ -0,0 +1,10 @@
+class @JSONEditingDescriptor extends XModule.Descriptor
+ constructor: (@element) ->
+ @edit_box = CodeMirror.fromTextArea($(".edit-box", @element)[0], {
+ mode: { name: "javascript", json: true }
+ lineNumbers: true
+ lineWrapping: true
+ })
+
+ save: ->
+ data: JSON.parse @edit_box.getValue()
diff --git a/cms/static/coffee/descriptor/0-79a64f2010d8b4cb8d0f0d6912c70c12.coffee b/cms/static/coffee/descriptor/0-79a64f2010d8b4cb8d0f0d6912c70c12.coffee
new file mode 100644
index 0000000000..68981e465b
--- /dev/null
+++ b/cms/static/coffee/descriptor/0-79a64f2010d8b4cb8d0f0d6912c70c12.coffee
@@ -0,0 +1,10 @@
+class @XMLEditingDescriptor extends XModule.Descriptor
+ constructor: (@element) ->
+ @edit_box = CodeMirror.fromTextArea($(".edit-box", @element)[0], {
+ mode: "xml"
+ lineNumbers: true
+ lineWrapping: true
+ })
+
+ save: ->
+ data: @edit_box.getValue()
diff --git a/cms/static/coffee/descriptor/0-c0f10bbfa80be09f66bef86df2e1152a.coffee b/cms/static/coffee/descriptor/0-c0f10bbfa80be09f66bef86df2e1152a.coffee
new file mode 100644
index 0000000000..e678a9b2eb
--- /dev/null
+++ b/cms/static/coffee/descriptor/0-c0f10bbfa80be09f66bef86df2e1152a.coffee
@@ -0,0 +1,11 @@
+class @HTMLEditingDescriptor
+ constructor: (@element) ->
+ @edit_box = CodeMirror.fromTextArea($(".edit-box", @element)[0], {
+ mode: "text/html"
+ lineNumbers: true
+ lineWrapping: true
+ })
+
+ save: ->
+ data: @edit_box.getValue()
+
diff --git a/cms/static/coffee/descriptor/0-fdfb7ccca8edfb703fc7a5f124a67f24.coffee b/cms/static/coffee/descriptor/0-fdfb7ccca8edfb703fc7a5f124a67f24.coffee
new file mode 100644
index 0000000000..7ce69e542a
--- /dev/null
+++ b/cms/static/coffee/descriptor/0-fdfb7ccca8edfb703fc7a5f124a67f24.coffee
@@ -0,0 +1,9 @@
+class @VerticalDescriptor extends XModule.Descriptor
+ constructor: (@element) ->
+ @$items = $(@element).find(".vert-mod")
+ @$items.sortable(
+ update: (event, ui) => @update()
+ )
+
+ save: ->
+ children: $('.vert-mod li', @element).map((idx, el) -> $(el).data('id')).toArray()
diff --git a/cms/static/sass/descriptor/_0-3d2f9902e9ecf2dac554cd9b9e661a89.scss b/cms/static/sass/descriptor/_0-3d2f9902e9ecf2dac554cd9b9e661a89.scss
new file mode 100644
index 0000000000..0dc07919ae
--- /dev/null
+++ b/cms/static/sass/descriptor/_0-3d2f9902e9ecf2dac554cd9b9e661a89.scss
@@ -0,0 +1,5 @@
+.CodeMirror {
+ background: #fff;
+ font-size: 13px;
+ color: #3c3c3c;
+}
\ No newline at end of file
diff --git a/cms/static/sass/descriptor/_module-styles.scss b/cms/static/sass/descriptor/_module-styles.scss
new file mode 100644
index 0000000000..81d1cc800f
--- /dev/null
+++ b/cms/static/sass/descriptor/_module-styles.scss
@@ -0,0 +1 @@
+.xmodule_edit.xmodule_DiscussionDescriptor { @import "0-3d2f9902e9ecf2dac554cd9b9e661a89.scss"; }.xmodule_edit.xmodule_CapaDescriptor { @import "0-3d2f9902e9ecf2dac554cd9b9e661a89.scss"; }.xmodule_edit.xmodule_ABTestDescriptor { @import "0-3d2f9902e9ecf2dac554cd9b9e661a89.scss"; }.xmodule_edit.xmodule_RawDescriptor { @import "0-3d2f9902e9ecf2dac554cd9b9e661a89.scss"; }.xmodule_edit.xmodule_ErrorDescriptor { @import "0-3d2f9902e9ecf2dac554cd9b9e661a89.scss"; }.xmodule_edit.xmodule_VideoDescriptor { @import "0-3d2f9902e9ecf2dac554cd9b9e661a89.scss"; }.xmodule_edit.xmodule_CustomTagDescriptor { @import "0-3d2f9902e9ecf2dac554cd9b9e661a89.scss"; }
\ No newline at end of file
diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
index 5d5e3a8eb8..da91c44af7 100644
--- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
@@ -15,8 +15,7 @@ class @Problem
@inputs = @$("[id^=input_#{problem_prefix}_]")
@$('section.action input:button').click @refreshAnswers
- @$('section.action input.check').click @check_fd
- #@$('section.action input.check').click @check
+ @$('section.action input.check').click @check
@$('section.action input.show').click @show
@$('section.action input.save').click @save
@@ -47,88 +46,6 @@ class @Problem
if setupMethod?
@inputtypeDisplays[id] = setupMethod(inputtype)
-
- ###
- # 'check_fd' uses FormData to allow file submissions in the 'problem_check' dispatch,
- # in addition to simple querystring-based answers
- #
- # NOTE: The dispatch 'problem_check' is being singled out for the use of FormData;
- # maybe preferable to consolidate all dispatches to use FormData
- ###
- check_fd: =>
- Logger.log 'problem_check', @answers
-
- # If there are no file inputs in the problem, we can fall back on @check
- if $('input:file').length == 0
- @check()
- return
-
- if not window.FormData
- alert "Submission aborted! Sorry, your browser does not support file uploads. If you can, please use Chrome or Safari which have been verified to support file uploads."
- return
-
- fd = new FormData()
-
- # Sanity checks on submission
- max_filesize = 4*1000*1000 # 4 MB
- file_too_large = false
- file_not_selected = false
- required_files_not_submitted = false
- unallowed_file_submitted = false
-
- errors = []
-
- @inputs.each (index, element) ->
- if element.type is 'file'
- required_files = $(element).data("required_files")
- allowed_files = $(element).data("allowed_files")
- for file in element.files
- if allowed_files.length != 0 and file.name not in allowed_files
- unallowed_file_submitted = true
- errors.push "You submitted #{file.name}; only #{allowed_files} are allowed."
- if file.name in required_files
- required_files.splice(required_files.indexOf(file.name), 1)
- if file.size > max_filesize
- file_too_large = true
- errors.push 'Your file "' + file.name '" is too large (max size: ' + max_filesize/(1000*1000) + ' MB)'
- fd.append(element.id, file)
- if element.files.length == 0
- file_not_selected = true
- fd.append(element.id, '') # In case we want to allow submissions with no file
- if required_files.length != 0
- required_files_not_submitted = true
- errors.push "You did not submit the required files: #{required_files}."
- else
- fd.append(element.id, element.value)
-
-
- if file_not_selected
- errors.push 'You did not select any files to submit'
-
- error_html = '
\n'
- for error in errors
- error_html += '
' + error + '
\n'
- error_html += '
'
- @gentle_alert error_html
-
- abort_submission = file_too_large or file_not_selected or unallowed_file_submitted or required_files_not_submitted
-
- settings =
- type: "POST"
- data: fd
- processData: false
- contentType: false
- success: (response) =>
- switch response.success
- when 'incorrect', 'correct'
- @render(response.contents)
- @updateProgress response
- else
- @gentle_alert response.success
-
- if not abort_submission
- $.ajaxWithPrefix("#{@url}/problem_check", settings)
-
check: =>
Logger.log 'problem_check', @answers
$.postWithPrefix "#{@url}/problem_check", @answers, (response) =>
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index 17aa3832d8..98b464d57e 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -26,6 +26,7 @@ class SelfAssessmentModule(XModule):
resource_string(__name__, 'js/src/selfassessment/display.coffee')
]
}
+
js_module_name = "SelfAssessmentModule"
def get_html(self):
@@ -36,22 +37,97 @@ class SelfAssessmentModule(XModule):
instance_state=None, shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, descriptor,
instance_state, shared_state, **kwargs)
- self.html = self.definition['data']
+
+ self.attempts = 0
+ self.max_attempts = None
+
+ dom2 = etree.fromstring(definition['data'])
+
+ self.max_attempts = self.metadata.get('attempts', None)
+ if self.max_attempts is not None:
+ self.max_attempts = int(self.max_attempts)
+
+ self.show_answer = self.metadata.get('showanswer', 'closed')
+
+ self.force_save_button = self.metadata.get('force_save_button', 'false')
+
+ if self.show_answer == "":
+ self.show_answer = "closed"
+
+ if instance_state is not None:
+ instance_state = json.loads(instance_state)
+ if instance_state is not None and 'attempts' in instance_state:
+ self.attempts = instance_state['attempts']
+
+ self.name = only_one(dom2.xpath('/problem/@name'))
+
+ self.seed = 1
+
+ try:
+ # TODO (vshnayder): move as much as possible of this work and error
+ # checking to descriptor load time
+ self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(),
+ instance_state, seed=self.seed, system=self.system)
+ except Exception as err:
+ msg = 'cannot create LoncapaProblem {loc}: {err}'.format(
+ loc=self.location.url(), err=err)
+ # TODO (vshnayder): do modules need error handlers too?
+ # We shouldn't be switching on DEBUG.
+ if self.system.DEBUG:
+ log.warning(msg)
+ # TODO (vshnayder): This logic should be general, not here--and may
+ # want to preserve the data instead of replacing it.
+ # e.g. in the CMS
+ msg = '
%s
' % msg.replace('<', '<')
+ msg += '
%s
' % traceback.format_exc().replace('<', '<')
+ # create a dummy problem with error message instead of failing
+ problem_text = (''
+ 'Problem %s has an error:%s' %
+ (self.location.url(), msg))
+ self.lcp = LoncapaProblem(
+ problem_text, self.location.html_id(),
+ instance_state, seed=self.seed, system=self.system)
+ else:
+ # add extra info and raise
+ raise Exception(msg), None, sys.exc_info()[2]
+
+ @staticmethod
+ def make_dict_of_responses(get):
+ '''Make dictionary of student responses (aka "answers")
+ get is POST dictionary.
+ '''
+ answers = dict()
+ for key in get:
+ # e.g. input_resistor_1 ==> resistor_1
+ _, _, name = key.partition('_')
+
+ # This allows for answers which require more than one value for
+ # the same form input (e.g. checkbox inputs). The convention is that
+ # if the name ends with '[]' (which looks like an array), then the
+ # answer will be an array.
+ if not name.endswith('[]'):
+ answers[name] = get[key]
+ else:
+ name = name[:-2]
+ answers[name] = get.getlist(key)
+
+ return answers
+
def handle_ajax(self, dispatch, get):
- '''
- This is called by courseware.module_render, to handle an AJAX call.
- "get" is request.POST.
+ '''
+ This is called by courseware.module_render, to handle an AJAX call.
+ "get" is request.POST.
- Returns a json dictionary:
- { 'progress_changed' : True/False,
- 'progress' : 'none'/'in_progress'/'done',
- }
- '''
+ Returns a json dictionary:
+ { 'progress_changed' : True/False,
+ 'progress' : 'none'/'in_progress'/'done',
+ }
+ '''
handlers = {
'problem_get': self.get_problem,
- 'problem_check': self.check_problem,
'problem_save': self.save_problem,
+ 'problem_check': self.check_problem,
}
if dispatch not in handlers:
@@ -66,136 +142,314 @@ class SelfAssessmentModule(XModule):
})
return json.dumps(d, cls=ComplexEncoder)
+ def save_problem(self, get):
+ '''
+ Save the passed in answers.
+ Returns a dict { 'success' : bool, ['error' : error-msg]},
+ with the error key only present if success is False.
+ '''
+ event_info = dict()
+ event_info['state'] = self.lcp.get_state()
+ event_info['problem_id'] = self.location.url()
+
+ answers = self.make_dict_of_responses(get)
+ event_info['answers'] = answers
+
+ self.lcp.student_answers = answers
+
+ # TODO: should this be save_problem_fail? Looks like success to me...
+ self.system.track_function('save_problem_fail', event_info)
+ return {'success': True}
+
+ def check_problem(self, get):
+ ''' Checks whether answers to a problem are correct, and
+ returns a map of correct/incorrect answers:
+
+ {'success' : bool,
+ 'contents' : html}
+ '''
+ event_info = dict()
+ event_info['state'] = self.lcp.get_state()
+ event_info['problem_id'] = self.location.url()
+
+ answers = self.make_dict_of_responses(get)
+ event_info['answers'] = convert_files_to_filenames(answers)
+
+ try:
+ old_state = self.lcp.get_state()
+ lcp_id = self.lcp.problem_id
+ correct_map = self.lcp.grade_answers(answers)
+ except StudentInputError as inst:
+ # TODO (vshnayder): why is this line here?
+ #self.lcp = LoncapaProblem(self.definition['data'],
+ # id=lcp_id, state=old_state, system=self.system)
+ log.exception("StudentInputError in capa_module:problem_check")
+ return {'success': inst.message}
+ except Exception, err:
+ # TODO: why is this line here?
+ #self.lcp = LoncapaProblem(self.definition['data'],
+ # id=lcp_id, state=old_state, system=self.system)
+ if self.system.DEBUG:
+ msg = "Error checking problem: " + str(err)
+ msg += '\nTraceback:\n' + traceback.format_exc()
+ return {'success': msg}
+ log.exception("Error in capa_module problem checking")
+ raise Exception("error in capa_module")
+
+ self.attempts = self.attempts + 1
+ self.lcp.done = True
+
+ # success = correct if ALL questions in this problem are correct
+ success = 'correct'
+ for answer_id in correct_map:
+ if not correct_map.is_correct(answer_id):
+ success = 'incorrect'
+
+ # NOTE: We are logging both full grading and queued-grading submissions. In the latter,
+ # 'success' will always be incorrect
+ event_info['correct_map'] = correct_map.get_dict()
+ event_info['success'] = success
+ event_info['attempts'] = self.attempts
+ self.system.track_function('save_problem_check', event_info)
+
+ if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback
+ self.system.psychometrics_handler(self.get_instance_state())
+
+ # render problem into HTML
+ html = self.get_problem_html(encapsulate=False)
+
+ return {'success': success,
+ 'contents': html,
+ }
+
+ # Figure out if we should move these to capa_problem?
+ def get_problem(self, get):
+ ''' Return results of get_problem_html, as a simple dict for json-ing.
+ { 'html': }
+
+ Used if we want to reconfirm we have the right thing e.g. after
+ several AJAX calls.
+ '''
+ return {'html': self.get_problem_html(encapsulate=False)}
+
+ @staticmethod
+ def make_dict_of_responses(get):
+ '''Make dictionary of student responses (aka "answers")
+ get is POST dictionary.
+ '''
+ answers = dict()
+ for key in get:
+ # e.g. input_resistor_1 ==> resistor_1
+ _, _, name = key.partition('_')
+
+ # This allows for answers which require more than one value for
+ # the same form input (e.g. checkbox inputs). The convention is that
+ # if the name ends with '[]' (which looks like an array), then the
+ # answer will be an array.
+ if not name.endswith('[]'):
+ answers[name] = get[key]
+ else:
+ name = name[:-2]
+ answers[name] = get.getlist(key)
+
+ return answers
+
+ def get_instance_state(self):
+ state = self.lcp.get_state()
+ state['attempts'] = self.attempts
+ return json.dumps(state)
+
+ def get_score(self):
+ return self.lcp.get_score()
+
+ def max_score(self):
+ return self.lcp.get_max_score()
+
+ def get_progress(self):
+ ''' For now, just return score / max_score
+ '''
+ d = self.get_score()
+ score = d['score']
+ total = d['total']
+ if total > 0:
+ try:
+ return Progress(score, total)
+ except Exception as err:
+ log.exception("Got bad progress")
+ return None
+ return None
+
+ def get_html(self):
+ return self.system.render_template('problem_ajax.html', {
+ 'element_id': self.location.html_id(),
+ 'id': self.id,
+ 'ajax_url': self.system.ajax_url,
+ })
+
+ def get_problem_html(self, encapsulate=True):
+ '''Return html for the problem. Adds check, reset, save buttons
+ as necessary based on the problem config and state.'''
+
+ try:
+ html = self.lcp.get_html()
+ except Exception, err:
+ log.exception(err)
+
+ # TODO (vshnayder): another switch on DEBUG.
+ if self.system.DEBUG:
+ msg = (
+ '[courseware.capa.capa_module] '
+ 'Failed to generate HTML for problem %s' %
+ (self.location.url()))
+ msg += '
Error:
%s
' % str(err).replace('<', '<')
+ msg += '
%s
' % traceback.format_exc().replace('<', '<')
+ html = msg
+ else:
+ # We're in non-debug mode, and possibly even in production. We want
+ # to avoid bricking of problem as much as possible
+
+ # Presumably, student submission has corrupted LoncapaProblem HTML.
+ # First, pull down all student answers
+ student_answers = self.lcp.student_answers
+ answer_ids = student_answers.keys()
+
+ # Some inputtypes, such as dynamath, have additional "hidden" state that
+ # is not exposed to the student. Keep those hidden
+ # TODO: Use regex, e.g. 'dynamath' is suffix at end of answer_id
+ hidden_state_keywords = ['dynamath']
+ for answer_id in answer_ids:
+ for hidden_state_keyword in hidden_state_keywords:
+ if answer_id.find(hidden_state_keyword) >= 0:
+ student_answers.pop(answer_id)
+
+ # Next, generate a fresh LoncapaProblem
+ self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(),
+ state=None, # Tabula rasa
+ seed=self.seed, system=self.system)
+
+ # Prepend a scary warning to the student
+ warning = '
'\
+ '
Warning: The problem has been reset to its initial state!
'\
+ 'The problem\'s state was corrupted by an invalid submission. '\
+ 'The submission consisted of:'\
+ '
'
+ for student_answer in student_answers.values():
+ if student_answer != '':
+ warning += '
' + cgi.escape(student_answer) + '
'
+ warning += '
'\
+ 'If this error persists, please contact the course staff.'\
+ '
'
+
+ html = warning
+ try:
+ html += self.lcp.get_html()
+ except Exception, err: # Couldn't do it. Give up
+ log.exception(err)
+ raise
+
+ content = {'name': self.display_name,
+ 'html': html,
+ 'weight': self.descriptor.weight,
+ }
+
+ # We using strings as truthy values, because the terminology of the
+ # check button is context-specific.
+
+ # Put a "Check" button if unlimited attempts or still some left
+ if self.max_attempts is None or self.attempts < self.max_attempts-1:
+ check_button = "Check"
+ else:
+ # Will be final check so let user know that
+ check_button = "Final Check"
+
+ reset_button = True
+ save_button = True
+
+ # If we're after deadline, or user has exhausted attempts,
+ # question is read-only.
+ if self.closed():
+ check_button = False
+ reset_button = False
+ save_button = False
+
+ # User submitted a problem, and hasn't reset. We don't want
+ # more submissions.
+ if self.lcp.done and self.rerandomize == "always":
+ check_button = False
+ save_button = False
+
+ # Only show the reset button if pressing it will show different values
+ if self.rerandomize not in ["always", "onreset"]:
+ reset_button = False
+
+ # User hasn't submitted an answer yet -- we don't want resets
+ if not self.lcp.done:
+ reset_button = False
+
+ # We may not need a "save" button if infinite number of attempts and
+ # non-randomized. The problem author can force it. It's a bit weird for
+ # randomization to control this; should perhaps be cleaned up.
+ if (self.force_save_button == "false") and (self.max_attempts is None and self.rerandomize != "always"):
+ save_button = False
+
+ context = {'problem': content,
+ 'id': self.id,
+ 'check_button': check_button,
+ 'reset_button': reset_button,
+ 'save_button': save_button,
+ 'answer_available': self.answer_available(),
+ 'ajax_url': self.system.ajax_url,
+ 'attempts_used': self.attempts,
+ 'attempts_allowed': self.max_attempts,
+ 'progress': self.get_progress(),
+ }
+
+ html = self.system.render_template('problem.html', context)
+ if encapsulate:
+ html = '
'.format(
+ id=self.location.html_id(), ajax_url=self.system.ajax_url) + html + "
"
+
+ # cdodge: OK, we have to do two rounds of url reference subsitutions
+ # one which uses the 'asset library' that is served by the contentstore and the
+ # more global /static/ filesystem based static content.
+ # NOTE: rewrite_content_links is defined in XModule
+ # This is a bit unfortunate and I'm sure we'll try to considate this into
+ # a one step process.
+ html = rewrite_links(html, self.rewrite_content_links)
+
+ # now do the substitutions which are filesystem based, e.g. '/static/' prefixes
+ return self.system.replace_urls(html, self.metadata['data_dir'])
+
class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
"""
Module for putting self assessment questions into a course
"""
- mako_template = "widgets/html-edit.html"
- module_class = SelfAssessmentModule
- filename_extension = "xml"
- template_dir_name = "selfassessment"
- js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]}
- js_module_name = "HTMLEditingDescriptor"
+ stores_state = True
+ has_score = True
+ template_dir_name = 'selfassessment'
- # VS[compat] TODO (cpennington): Delete this method once all fall 2012 course
- # are being edited in the cms
+ # Capa modules have some additional metadata:
+ # TODO (vshnayder): do problems have any other metadata? Do they
+ # actually use type and points?
+ metadata_attributes = RawDescriptor.metadata_attributes + ('type', 'points')
+
+ # VS[compat]
+ # TODO (cpennington): Delete this method once all fall 2012 course are being
+ # edited in the cms
@classmethod
def backcompat_paths(cls, path):
- if path.endswith('.html.xml'):
- path = path[:-9] + '.html' # backcompat--look for html instead of xml
- if path.endswith('.html.html'):
- path = path[:-5] # some people like to include .html in filenames..
- candidates = []
- while os.sep in path:
- candidates.append(path)
- _, _, path = path.partition(os.sep)
+ return [
+ 'problems/' + path[8:],
+ path[8:],
+ ]
- # also look for .html versions instead of .xml
- nc = []
- for candidate in candidates:
- if candidate.endswith('.xml'):
- nc.append(candidate[:-4] + '.html')
- return candidates + nc
+ def __init__(self, *args, **kwargs):
+ super(CapaDescriptor, self).__init__(*args, **kwargs)
- # NOTE: html descriptors are special. We do not want to parse and
- # export them ourselves, because that can break things (e.g. lxml
- # adds body tags when it exports, but they should just be html
- # snippets that will be included in the middle of pages.
-
- @classmethod
- def load_definition(cls, xml_object, system, location):
- '''Load a descriptor from the specified xml_object:
-
- If there is a filename attribute, load it as a string, and
- log a warning if it is not parseable by etree.HTMLParser.
-
- If there is not a filename attribute, the definition is the body
- of the xml_object, without the root tag (do not want in the
- middle of a page)
- '''
- filename = xml_object.get('filename')
- if filename is None:
- definition_xml = copy.deepcopy(xml_object)
- cls.clean_metadata_from_xml(definition_xml)
- return {'data': stringify_children(definition_xml)}
+ weight_string = self.metadata.get('weight', None)
+ if weight_string:
+ self.weight = float(weight_string)
else:
- # html is special. cls.filename_extension is 'xml', but
- # if 'filename' is in the definition, that means to load
- # from .html
- # 'filename' in html pointers is a relative path
- # (not same as 'html/blah.html' when the pointer is in a directory itself)
- pointer_path = "{category}/{url_path}".format(category='html',
- url_path=name_to_pathname(location.name))
- base = path(pointer_path).dirname()
- #log.debug("base = {0}, base.dirname={1}, filename={2}".format(base, base.dirname(), filename))
- filepath = "{base}/{name}.html".format(base=base, name=filename)
- #log.debug("looking for html file for {0} at {1}".format(location, filepath))
-
-
-
- # VS[compat]
- # TODO (cpennington): If the file doesn't exist at the right path,
- # give the class a chance to fix it up. The file will be written out
- # again in the correct format. This should go away once the CMS is
- # online and has imported all current (fall 2012) courses from xml
- if not system.resources_fs.exists(filepath):
- candidates = cls.backcompat_paths(filepath)
- #log.debug("candidates = {0}".format(candidates))
- for candidate in candidates:
- if system.resources_fs.exists(candidate):
- filepath = candidate
- break
-
- try:
- with system.resources_fs.open(filepath) as file:
- html = file.read()
- # Log a warning if we can't parse the file, but don't error
- if not check_html(html):
- msg = "Couldn't parse html in {0}.".format(filepath)
- log.warning(msg)
- system.error_tracker("Warning: " + msg)
-
- definition = {'data': html}
-
- # TODO (ichuang): remove this after migration
- # for Fall 2012 LMS migration: keep filename (and unmangled filename)
- definition['filename'] = [ filepath, filename ]
-
- return definition
-
- except (ResourceNotFoundError) as err:
- msg = 'Unable to load file contents at path {0}: {1} '.format(
- filepath, err)
- # add more info and re-raise
- raise Exception(msg), None, sys.exc_info()[2]
-
- # TODO (vshnayder): make export put things in the right places.
-
- def definition_to_xml(self, resource_fs):
- '''If the contents are valid xml, write them to filename.xml. Otherwise,
- write just to filename.xml, and the html
- string to filename.html.
- '''
- try:
- return etree.fromstring(self.definition['data'])
- except etree.XMLSyntaxError:
- pass
-
- # Not proper format. Write html to file, return an empty tag
- pathname = name_to_pathname(self.url_name)
- pathdir = path(pathname).dirname()
- filepath = u'{category}/{pathname}.html'.format(category=self.category,
- pathname=pathname)
-
- resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True)
- with resource_fs.open(filepath, 'w') as file:
- file.write(self.definition['data'])
-
- # write out the relative name
- relname = path(pathname).basename()
-
- elt = etree.Element('html')
- elt.set("filename", relname)
- return elt
+ self.weight = None
From 64c791b38a63e38eb2d2fa6fc80e088b2874e9ef Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Wed, 31 Oct 2012 16:18:55 -0400
Subject: [PATCH 007/121] working on self assessment module
---
.../xmodule/xmodule/self_assessment_module.py | 535 ++++++------------
1 file changed, 163 insertions(+), 372 deletions(-)
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index 98b464d57e..df69213281 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -1,24 +1,41 @@
-import copy
-from fs.errors import ResourceNotFoundError
+import cgi
+import datetime
+import dateutil
+import dateutil.parser
+import json
import logging
-import os
+import traceback
+import re
import sys
+
+from datetime import timedelta
from lxml import etree
from lxml.html import rewrite_links
-from path import path
-
-from .x_module import XModule
from pkg_resources import resource_string
-from .xml_module import XmlDescriptor, name_to_pathname
-from .editing_module import EditingDescriptor
-from .stringify import stringify_children
-from .html_checker import check_html
-from xmodule.modulestore import Location
-from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent
+from capa.capa_problem import LoncapaProblem
+from capa.responsetypes import StudentInputError
+from capa.util import convert_files_to_filenames
+from progress import Progress
+from xmodule.x_module import XModule
+from xmodule.raw_module import RawDescriptor
+from xmodule.exceptions import NotFoundError
log = logging.getLogger("mitx.courseware")
+def only_one(lst, default="", process=lambda x: x):
+ """
+ If lst is empty, returns default
+ If lst has a single element, applies process to that element and returns it
+ Otherwise, raises an exeception
+ """
+ if len(lst) == 0:
+ return default
+ elif len(lst) == 1:
+ return process(lst[0])
+ else:
+ raise Exception('Malformed XML')
+
class SelfAssessmentModule(XModule):
js = {'coffee': [resource_string(__name__, 'js/src/javascript_loader.coffee'),
@@ -26,7 +43,6 @@ class SelfAssessmentModule(XModule):
resource_string(__name__, 'js/src/selfassessment/display.coffee')
]
}
-
js_module_name = "SelfAssessmentModule"
def get_html(self):
@@ -38,22 +54,15 @@ class SelfAssessmentModule(XModule):
XModule.__init__(self, system, location, definition, descriptor,
instance_state, shared_state, **kwargs)
+ dom2 = etree.fromstring(definition['data'])
+
self.attempts = 0
self.max_attempts = None
- dom2 = etree.fromstring(definition['data'])
-
self.max_attempts = self.metadata.get('attempts', None)
if self.max_attempts is not None:
self.max_attempts = int(self.max_attempts)
- self.show_answer = self.metadata.get('showanswer', 'closed')
-
- self.force_save_button = self.metadata.get('force_save_button', 'false')
-
- if self.show_answer == "":
- self.show_answer = "closed"
-
if instance_state is not None:
instance_state = json.loads(instance_state)
if instance_state is not None and 'attempts' in instance_state:
@@ -61,198 +70,11 @@ class SelfAssessmentModule(XModule):
self.name = only_one(dom2.xpath('/problem/@name'))
- self.seed = 1
+ self.rubric=etree.tostring(only_one(dom2.xpath("/rubric")))
+ self.problem=etree.tostring(only_one(dom2.xpath("/problem")))
- try:
- # TODO (vshnayder): move as much as possible of this work and error
- # checking to descriptor load time
- self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(),
- instance_state, seed=self.seed, system=self.system)
- except Exception as err:
- msg = 'cannot create LoncapaProblem {loc}: {err}'.format(
- loc=self.location.url(), err=err)
- # TODO (vshnayder): do modules need error handlers too?
- # We shouldn't be switching on DEBUG.
- if self.system.DEBUG:
- log.warning(msg)
- # TODO (vshnayder): This logic should be general, not here--and may
- # want to preserve the data instead of replacing it.
- # e.g. in the CMS
- msg = '
%s
' % msg.replace('<', '<')
- msg += '
%s
' % traceback.format_exc().replace('<', '<')
- # create a dummy problem with error message instead of failing
- problem_text = (''
- 'Problem %s has an error:%s' %
- (self.location.url(), msg))
- self.lcp = LoncapaProblem(
- problem_text, self.location.html_id(),
- instance_state, seed=self.seed, system=self.system)
- else:
- # add extra info and raise
- raise Exception(msg), None, sys.exc_info()[2]
-
- @staticmethod
- def make_dict_of_responses(get):
- '''Make dictionary of student responses (aka "answers")
- get is POST dictionary.
- '''
- answers = dict()
- for key in get:
- # e.g. input_resistor_1 ==> resistor_1
- _, _, name = key.partition('_')
-
- # This allows for answers which require more than one value for
- # the same form input (e.g. checkbox inputs). The convention is that
- # if the name ends with '[]' (which looks like an array), then the
- # answer will be an array.
- if not name.endswith('[]'):
- answers[name] = get[key]
- else:
- name = name[:-2]
- answers[name] = get.getlist(key)
-
- return answers
-
-
- def handle_ajax(self, dispatch, get):
- '''
- This is called by courseware.module_render, to handle an AJAX call.
- "get" is request.POST.
-
- Returns a json dictionary:
- { 'progress_changed' : True/False,
- 'progress' : 'none'/'in_progress'/'done',
- }
- '''
- handlers = {
- 'problem_get': self.get_problem,
- 'problem_save': self.save_problem,
- 'problem_check': self.check_problem,
- }
-
- if dispatch not in handlers:
- return 'Error'
-
- before = self.get_progress()
- d = handlers[dispatch](get)
- after = self.get_progress()
- d.update({
- 'progress_changed': after != before,
- 'progress_status': Progress.to_js_status_str(after),
- })
- return json.dumps(d, cls=ComplexEncoder)
-
- def save_problem(self, get):
- '''
- Save the passed in answers.
- Returns a dict { 'success' : bool, ['error' : error-msg]},
- with the error key only present if success is False.
- '''
- event_info = dict()
- event_info['state'] = self.lcp.get_state()
- event_info['problem_id'] = self.location.url()
-
- answers = self.make_dict_of_responses(get)
- event_info['answers'] = answers
-
- self.lcp.student_answers = answers
-
- # TODO: should this be save_problem_fail? Looks like success to me...
- self.system.track_function('save_problem_fail', event_info)
- return {'success': True}
-
- def check_problem(self, get):
- ''' Checks whether answers to a problem are correct, and
- returns a map of correct/incorrect answers:
-
- {'success' : bool,
- 'contents' : html}
- '''
- event_info = dict()
- event_info['state'] = self.lcp.get_state()
- event_info['problem_id'] = self.location.url()
-
- answers = self.make_dict_of_responses(get)
- event_info['answers'] = convert_files_to_filenames(answers)
-
- try:
- old_state = self.lcp.get_state()
- lcp_id = self.lcp.problem_id
- correct_map = self.lcp.grade_answers(answers)
- except StudentInputError as inst:
- # TODO (vshnayder): why is this line here?
- #self.lcp = LoncapaProblem(self.definition['data'],
- # id=lcp_id, state=old_state, system=self.system)
- log.exception("StudentInputError in capa_module:problem_check")
- return {'success': inst.message}
- except Exception, err:
- # TODO: why is this line here?
- #self.lcp = LoncapaProblem(self.definition['data'],
- # id=lcp_id, state=old_state, system=self.system)
- if self.system.DEBUG:
- msg = "Error checking problem: " + str(err)
- msg += '\nTraceback:\n' + traceback.format_exc()
- return {'success': msg}
- log.exception("Error in capa_module problem checking")
- raise Exception("error in capa_module")
-
- self.attempts = self.attempts + 1
- self.lcp.done = True
-
- # success = correct if ALL questions in this problem are correct
- success = 'correct'
- for answer_id in correct_map:
- if not correct_map.is_correct(answer_id):
- success = 'incorrect'
-
- # NOTE: We are logging both full grading and queued-grading submissions. In the latter,
- # 'success' will always be incorrect
- event_info['correct_map'] = correct_map.get_dict()
- event_info['success'] = success
- event_info['attempts'] = self.attempts
- self.system.track_function('save_problem_check', event_info)
-
- if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback
- self.system.psychometrics_handler(self.get_instance_state())
-
- # render problem into HTML
- html = self.get_problem_html(encapsulate=False)
-
- return {'success': success,
- 'contents': html,
- }
-
- # Figure out if we should move these to capa_problem?
- def get_problem(self, get):
- ''' Return results of get_problem_html, as a simple dict for json-ing.
- { 'html': }
-
- Used if we want to reconfirm we have the right thing e.g. after
- several AJAX calls.
- '''
- return {'html': self.get_problem_html(encapsulate=False)}
-
- @staticmethod
- def make_dict_of_responses(get):
- '''Make dictionary of student responses (aka "answers")
- get is POST dictionary.
- '''
- answers = dict()
- for key in get:
- # e.g. input_resistor_1 ==> resistor_1
- _, _, name = key.partition('_')
-
- # This allows for answers which require more than one value for
- # the same form input (e.g. checkbox inputs). The convention is that
- # if the name ends with '[]' (which looks like an array), then the
- # answer will be an array.
- if not name.endswith('[]'):
- answers[name] = get[key]
- else:
- name = name[:-2]
- answers[name] = get.getlist(key)
-
- return answers
+ self.lcp = LoncapaProblem(self.problem, self.location.html_id(),
+ instance_state, seed=self.seed, system=self.system)
def get_instance_state(self):
state = self.lcp.get_state()
@@ -260,14 +82,14 @@ class SelfAssessmentModule(XModule):
return json.dumps(state)
def get_score(self):
- return self.lcp.get_score()
+ return self.lcp.get_score()
def max_score(self):
return self.lcp.get_max_score()
- def get_progress(self):
- ''' For now, just return score / max_score
- '''
+ def get_progress(self):
+ ''' For now, just return score / max_score
+ '''
d = self.get_score()
score = d['score']
total = d['total']
@@ -286,170 +108,139 @@ class SelfAssessmentModule(XModule):
'ajax_url': self.system.ajax_url,
})
- def get_problem_html(self, encapsulate=True):
- '''Return html for the problem. Adds check, reset, save buttons
- as necessary based on the problem config and state.'''
-
- try:
- html = self.lcp.get_html()
- except Exception, err:
- log.exception(err)
-
- # TODO (vshnayder): another switch on DEBUG.
- if self.system.DEBUG:
- msg = (
- '[courseware.capa.capa_module] '
- 'Failed to generate HTML for problem %s' %
- (self.location.url()))
- msg += '
Error:
%s
' % str(err).replace('<', '<')
- msg += '
%s
' % traceback.format_exc().replace('<', '<')
- html = msg
- else:
- # We're in non-debug mode, and possibly even in production. We want
- # to avoid bricking of problem as much as possible
-
- # Presumably, student submission has corrupted LoncapaProblem HTML.
- # First, pull down all student answers
- student_answers = self.lcp.student_answers
- answer_ids = student_answers.keys()
-
- # Some inputtypes, such as dynamath, have additional "hidden" state that
- # is not exposed to the student. Keep those hidden
- # TODO: Use regex, e.g. 'dynamath' is suffix at end of answer_id
- hidden_state_keywords = ['dynamath']
- for answer_id in answer_ids:
- for hidden_state_keyword in hidden_state_keywords:
- if answer_id.find(hidden_state_keyword) >= 0:
- student_answers.pop(answer_id)
-
- # Next, generate a fresh LoncapaProblem
- self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(),
- state=None, # Tabula rasa
- seed=self.seed, system=self.system)
-
- # Prepend a scary warning to the student
- warning = '
'\
- '
Warning: The problem has been reset to its initial state!
'\
- 'The problem\'s state was corrupted by an invalid submission. '\
- 'The submission consisted of:'\
- '
'
- for student_answer in student_answers.values():
- if student_answer != '':
- warning += '
' + cgi.escape(student_answer) + '
'
- warning += '
'\
- 'If this error persists, please contact the course staff.'\
- '
'
-
- html = warning
- try:
- html += self.lcp.get_html()
- except Exception, err: # Couldn't do it. Give up
- log.exception(err)
- raise
-
- content = {'name': self.display_name,
- 'html': html,
- 'weight': self.descriptor.weight,
- }
-
- # We using strings as truthy values, because the terminology of the
- # check button is context-specific.
-
- # Put a "Check" button if unlimited attempts or still some left
- if self.max_attempts is None or self.attempts < self.max_attempts-1:
- check_button = "Check"
- else:
- # Will be final check so let user know that
- check_button = "Final Check"
-
- reset_button = True
- save_button = True
-
- # If we're after deadline, or user has exhausted attempts,
- # question is read-only.
- if self.closed():
- check_button = False
- reset_button = False
- save_button = False
-
- # User submitted a problem, and hasn't reset. We don't want
- # more submissions.
- if self.lcp.done and self.rerandomize == "always":
- check_button = False
- save_button = False
-
- # Only show the reset button if pressing it will show different values
- if self.rerandomize not in ["always", "onreset"]:
- reset_button = False
-
- # User hasn't submitted an answer yet -- we don't want resets
- if not self.lcp.done:
- reset_button = False
-
- # We may not need a "save" button if infinite number of attempts and
- # non-randomized. The problem author can force it. It's a bit weird for
- # randomization to control this; should perhaps be cleaned up.
- if (self.force_save_button == "false") and (self.max_attempts is None and self.rerandomize != "always"):
- save_button = False
-
- context = {'problem': content,
- 'id': self.id,
- 'check_button': check_button,
- 'reset_button': reset_button,
- 'save_button': save_button,
- 'answer_available': self.answer_available(),
- 'ajax_url': self.system.ajax_url,
- 'attempts_used': self.attempts,
- 'attempts_allowed': self.max_attempts,
- 'progress': self.get_progress(),
- }
-
- html = self.system.render_template('problem.html', context)
- if encapsulate:
- html = '
'.format(
- id=self.location.html_id(), ajax_url=self.system.ajax_url) + html + "
"
-
- # cdodge: OK, we have to do two rounds of url reference subsitutions
- # one which uses the 'asset library' that is served by the contentstore and the
- # more global /static/ filesystem based static content.
- # NOTE: rewrite_content_links is defined in XModule
- # This is a bit unfortunate and I'm sure we'll try to considate this into
- # a one step process.
- html = rewrite_links(html, self.rewrite_content_links)
-
- # now do the substitutions which are filesystem based, e.g. '/static/' prefixes
- return self.system.replace_urls(html, self.metadata['data_dir'])
class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
"""
- Module for putting self assessment questions into a course
+ Module for putting raw html in a course
"""
+ mako_template = "widgets/html-edit.html"
+ module_class = HtmlModule
+ filename_extension = "xml"
+ template_dir_name = "html"
+ stores_state=True
+ has_score=True
- stores_state = True
- has_score = True
- template_dir_name = 'selfassessment'
+ js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/edit.coffee')]}
+ js_module_name = "HTMLEditingDescriptor"
- # Capa modules have some additional metadata:
- # TODO (vshnayder): do problems have any other metadata? Do they
- # actually use type and points?
- metadata_attributes = RawDescriptor.metadata_attributes + ('type', 'points')
-
- # VS[compat]
- # TODO (cpennington): Delete this method once all fall 2012 course are being
- # edited in the cms
+ # VS[compat] TODO (cpennington): Delete this method once all fall 2012 course
+ # are being edited in the cms
@classmethod
def backcompat_paths(cls, path):
- return [
- 'problems/' + path[8:],
- path[8:],
- ]
+ if path.endswith('.html.xml'):
+ path = path[:-9] + '.html' # backcompat--look for html instead of xml
+ if path.endswith('.html.html'):
+ path = path[:-5] # some people like to include .html in filenames..
+ candidates = []
+ while os.sep in path:
+ candidates.append(path)
+ _, _, path = path.partition(os.sep)
- def __init__(self, *args, **kwargs):
- super(CapaDescriptor, self).__init__(*args, **kwargs)
+ # also look for .html versions instead of .xml
+ nc = []
+ for candidate in candidates:
+ if candidate.endswith('.xml'):
+ nc.append(candidate[:-4] + '.html')
+ return candidates + nc
- weight_string = self.metadata.get('weight', None)
- if weight_string:
- self.weight = float(weight_string)
+ # NOTE: html descriptors are special. We do not want to parse and
+ # export them ourselves, because that can break things (e.g. lxml
+ # adds body tags when it exports, but they should just be html
+ # snippets that will be included in the middle of pages.
+
+ @classmethod
+ def load_definition(cls, xml_object, system, location):
+ '''Load a descriptor from the specified xml_object:
+
+ If there is a filename attribute, load it as a string, and
+ log a warning if it is not parseable by etree.HTMLParser.
+
+ If there is not a filename attribute, the definition is the body
+ of the xml_object, without the root tag (do not want in the
+ middle of a page)
+ '''
+ filename = xml_object.get('filename')
+ if filename is None:
+ definition_xml = copy.deepcopy(xml_object)
+ cls.clean_metadata_from_xml(definition_xml)
+ return {'data': stringify_children(definition_xml)}
else:
- self.weight = None
+ # html is special. cls.filename_extension is 'xml', but
+ # if 'filename' is in the definition, that means to load
+ # from .html
+ # 'filename' in html pointers is a relative path
+ # (not same as 'html/blah.html' when the pointer is in a directory itself)
+ pointer_path = "{category}/{url_path}".format(category='html',
+ url_path=name_to_pathname(location.name))
+ base = path(pointer_path).dirname()
+ #log.debug("base = {0}, base.dirname={1}, filename={2}".format(base, base.dirname(), filename))
+ filepath = "{base}/{name}.html".format(base=base, name=filename)
+ #log.debug("looking for html file for {0} at {1}".format(location, filepath))
+
+
+
+ # VS[compat]
+ # TODO (cpennington): If the file doesn't exist at the right path,
+ # give the class a chance to fix it up. The file will be written out
+ # again in the correct format. This should go away once the CMS is
+ # online and has imported all current (fall 2012) courses from xml
+ if not system.resources_fs.exists(filepath):
+ candidates = cls.backcompat_paths(filepath)
+ #log.debug("candidates = {0}".format(candidates))
+ for candidate in candidates:
+ if system.resources_fs.exists(candidate):
+ filepath = candidate
+ break
+
+ try:
+ with system.resources_fs.open(filepath) as file:
+ html = file.read()
+ # Log a warning if we can't parse the file, but don't error
+ if not check_html(html):
+ msg = "Couldn't parse html in {0}.".format(filepath)
+ log.warning(msg)
+ system.error_tracker("Warning: " + msg)
+
+ definition = {'data': html}
+
+ # TODO (ichuang): remove this after migration
+ # for Fall 2012 LMS migration: keep filename (and unmangled filename)
+ definition['filename'] = [ filepath, filename ]
+
+ return definition
+
+ except (ResourceNotFoundError) as err:
+ msg = 'Unable to load file contents at path {0}: {1} '.format(
+ filepath, err)
+ # add more info and re-raise
+ raise Exception(msg), None, sys.exc_info()[2]
+
+ # TODO (vshnayder): make export put things in the right places.
+
+ def definition_to_xml(self, resource_fs):
+ '''If the contents are valid xml, write them to filename.xml. Otherwise,
+ write just to filename.xml, and the html
+ string to filename.html.
+ '''
+ try:
+ return etree.fromstring(self.definition['data'])
+ except etree.XMLSyntaxError:
+ pass
+
+ # Not proper format. Write html to file, return an empty tag
+ pathname = name_to_pathname(self.url_name)
+ pathdir = path(pathname).dirname()
+ filepath = u'{category}/{pathname}.html'.format(category=self.category,
+ pathname=pathname)
+
+ resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True)
+ with resource_fs.open(filepath, 'w') as file:
+ file.write(self.definition['data'])
+
+ # write out the relative name
+ relname = path(pathname).basename()
+
+ elt = etree.Element('html')
+ elt.set("filename", relname)
+ return elt
From b74a484f75830e1ae92782f1f329ee8a25b63031 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Wed, 31 Oct 2012 16:39:04 -0400
Subject: [PATCH 008/121] working on self assessment
---
common/lib/xmodule/xmodule/capa_module.py | 306 ++++++++--------
.../xmodule/xmodule/self_assessment_module.py | 335 ++++++++++++++++++
2 files changed, 488 insertions(+), 153 deletions(-)
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index 151c726f66..31106a4aa8 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -29,10 +29,10 @@ TIMEDELTA_REGEX = re.compile(r'^((?P\d+?) day(?:s?))?(\s)?((?P\d+?)
def only_one(lst, default="", process=lambda x: x):
"""
- If lst is empty, returns default
- If lst has a single element, applies process to that element and returns it
- Otherwise, raises an exeception
- """
+If lst is empty, returns default
+If lst has a single element, applies process to that element and returns it
+Otherwise, raises an exeception
+"""
if len(lst) == 0:
return default
elif len(lst) == 1:
@@ -43,14 +43,14 @@ def only_one(lst, default="", process=lambda x: x):
def parse_timedelta(time_str):
"""
- time_str: A string with the following components:
- day[s] (optional)
- hour[s] (optional)
- minute[s] (optional)
- second[s] (optional)
+time_str: A string with the following components:
+ day[s] (optional)
+ hour[s] (optional)
+ minute[s] (optional)
+ second[s] (optional)
- Returns a datetime.timedelta parsed from the string
- """
+Returns a datetime.timedelta parsed from the string
+"""
parts = TIMEDELTA_REGEX.match(time_str)
if not parts:
return
@@ -71,15 +71,15 @@ class ComplexEncoder(json.JSONEncoder):
class CapaModule(XModule):
'''
- An XModule implementing LonCapa format problems, implemented by way of
- capa.capa_problem.LoncapaProblem
- '''
+An XModule implementing LonCapa format problems, implemented by way of
+capa.capa_problem.LoncapaProblem
+'''
icon_class = 'problem'
js = {'coffee': [resource_string(__name__, 'js/src/capa/display.coffee'),
resource_string(__name__, 'js/src/collapsible.coffee'),
resource_string(__name__, 'js/src/javascript_loader.coffee'),
- ],
+ ],
'js': [resource_string(__name__, 'js/src/capa/imageinput.js'),
resource_string(__name__, 'js/src/capa/schematic.js')]}
@@ -89,7 +89,7 @@ class CapaModule(XModule):
def __init__(self, system, location, definition, descriptor, instance_state=None,
shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, descriptor, instance_state,
- shared_state, **kwargs)
+ shared_state, **kwargs)
self.attempts = 0
self.max_attempts = None
@@ -100,7 +100,7 @@ class CapaModule(XModule):
if display_due_date_string is not None:
self.display_due_date = dateutil.parser.parse(display_due_date_string)
#log.debug("Parsed " + display_due_date_string +
- # " to " + str(self.display_due_date))
+ # " to " + str(self.display_due_date))
else:
self.display_due_date = None
@@ -109,7 +109,7 @@ class CapaModule(XModule):
self.grace_period = parse_timedelta(grace_period_string)
self.close_date = self.display_due_date + self.grace_period
#log.debug("Then parsed " + grace_period_string +
- # " to closing date" + str(self.close_date))
+ # " to closing date" + str(self.close_date))
else:
self.grace_period = None
self.close_date = self.display_due_date
@@ -137,9 +137,9 @@ class CapaModule(XModule):
elif self.rerandomize == "per_student" and hasattr(self.system, 'id'):
# TODO: This line is badly broken:
# (1) We're passing student ID to xmodule.
- # (2) There aren't bins of students. -- we only want 10 or 20 randomizations, and want to assign students
- # to these bins, and may not want cohorts. So e.g. hash(your-id, problem_id) % num_bins.
- # - analytics really needs small number of bins.
+ # (2) There aren't bins of students. -- we only want 10 or 20 randomizations, and want to assign students
+ # to these bins, and may not want cohorts. So e.g. hash(your-id, problem_id) % num_bins.
+ # - analytics really needs small number of bins.
self.seed = system.id
else:
self.seed = None
@@ -148,7 +148,7 @@ class CapaModule(XModule):
# TODO (vshnayder): move as much as possible of this work and error
# checking to descriptor load time
self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(),
- instance_state, seed=self.seed, system=self.system)
+ instance_state, seed=self.seed, system=self.system)
except Exception as err:
msg = 'cannot create LoncapaProblem {loc}: {err}'.format(
loc=self.location.url(), err=err)
@@ -175,9 +175,9 @@ class CapaModule(XModule):
@property
def rerandomize(self):
"""
- Property accessor that returns self.metadata['rerandomize'] in a
- canonical form
- """
+Property accessor that returns self.metadata['rerandomize'] in a
+canonical form
+"""
rerandomize = self.metadata.get('rerandomize', 'always')
if rerandomize in ("", "always", "true"):
return "always"
@@ -203,7 +203,7 @@ class CapaModule(XModule):
def get_progress(self):
''' For now, just return score / max_score
- '''
+'''
d = self.get_score()
score = d['score']
total = d['total']
@@ -220,11 +220,11 @@ class CapaModule(XModule):
'element_id': self.location.html_id(),
'id': self.id,
'ajax_url': self.system.ajax_url,
- })
+ })
def get_problem_html(self, encapsulate=True):
- '''Return html for the problem. Adds check, reset, save buttons
- as necessary based on the problem config and state.'''
+ '''Return html for the problem. Adds check, reset, save buttons
+as necessary based on the problem config and state.'''
try:
html = self.lcp.get_html()
@@ -242,15 +242,15 @@ class CapaModule(XModule):
html = msg
else:
# We're in non-debug mode, and possibly even in production. We want
- # to avoid bricking of problem as much as possible
+ # to avoid bricking of problem as much as possible
# Presumably, student submission has corrupted LoncapaProblem HTML.
- # First, pull down all student answers
+ # First, pull down all student answers
student_answers = self.lcp.student_answers
answer_ids = student_answers.keys()
# Some inputtypes, such as dynamath, have additional "hidden" state that
- # is not exposed to the student. Keep those hidden
+ # is not exposed to the student. Keep those hidden
# TODO: Use regex, e.g. 'dynamath' is suffix at end of answer_id
hidden_state_keywords = ['dynamath']
for answer_id in answer_ids:
@@ -258,17 +258,17 @@ class CapaModule(XModule):
if answer_id.find(hidden_state_keyword) >= 0:
student_answers.pop(answer_id)
- # Next, generate a fresh LoncapaProblem
+ # Next, generate a fresh LoncapaProblem
self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(),
- state=None, # Tabula rasa
- seed=self.seed, system=self.system)
+ state=None, # Tabula rasa
+ seed=self.seed, system=self.system)
# Prepend a scary warning to the student
- warning = '
'\
- '
Warning: The problem has been reset to its initial state!
'\
- 'The problem\'s state was corrupted by an invalid submission. ' \
- 'The submission consisted of:'\
- '
'
+ warning = '
'\
+ '
Warning: The problem has been reset to its initial state!
'\
+ 'The problem\'s state was corrupted by an invalid submission. '\
+ 'The submission consisted of:'\
+ '
'
for student_answer in student_answers.values():
if student_answer != '':
warning += '
' + cgi.escape(student_answer) + '
'
@@ -292,11 +292,11 @@ class CapaModule(XModule):
# check button is context-specific.
# Put a "Check" button if unlimited attempts or still some left
- if self.max_attempts is None or self.attempts < self.max_attempts-1:
+ if self.max_attempts is None or self.attempts < self.max_attempts-1:
check_button = "Check"
else:
# Will be final check so let user know that
- check_button = "Final Check"
+ check_button = "Final Check"
reset_button = True
save_button = True
@@ -358,14 +358,14 @@ class CapaModule(XModule):
def handle_ajax(self, dispatch, get):
'''
- This is called by courseware.module_render, to handle an AJAX call.
- "get" is request.POST.
+This is called by courseware.module_render, to handle an AJAX call.
+"get" is request.POST.
- Returns a json dictionary:
- { 'progress_changed' : True/False,
- 'progress' : 'none'/'in_progress'/'done',
- }
- '''
+Returns a json dictionary:
+{ 'progress_changed' : True/False,
+'progress' : 'none'/'in_progress'/'done',
+ }
+'''
handlers = {
'problem_get': self.get_problem,
'problem_check': self.check_problem,
@@ -398,7 +398,7 @@ class CapaModule(XModule):
def answer_available(self):
''' Is the user allowed to see an answer?
- '''
+'''
if self.show_answer == '':
return False
@@ -425,26 +425,26 @@ class CapaModule(XModule):
def update_score(self, get):
"""
- Delivers grading response (e.g. from asynchronous code checking) to
- the capa problem, so its score can be updated
+Delivers grading response (e.g. from asynchronous code checking) to
+the capa problem, so its score can be updated
- 'get' must have a field 'response' which is a string that contains the
- grader's response
+'get' must have a field 'response' which is a string that contains the
+grader's response
- No ajax return is needed. Return empty dict.
- """
+No ajax return is needed. Return empty dict.
+"""
queuekey = get['queuekey']
score_msg = get['xqueue_body']
self.lcp.update_score(score_msg, queuekey)
- return dict() # No AJAX return is needed
+ return dict() # No AJAX return is needed
def get_answer(self, get):
'''
- For the "show answer" button.
+For the "show answer" button.
- Returns the answers: {'answers' : answers}
- '''
+Returns the answers: {'answers' : answers}
+'''
event_info = dict()
event_info['problem_id'] = self.location.url()
self.system.track_function('show_answer', event_info)
@@ -453,8 +453,8 @@ class CapaModule(XModule):
else:
answers = self.lcp.get_question_answers()
- # answers (eg ) may have embedded images
- # but be careful, some problems are using non-string answer dicts
+ # answers (eg ) may have embedded images
+ # but be careful, some problems are using non-string answer dicts
new_answers = dict()
for answer_id in answers:
try:
@@ -469,18 +469,18 @@ class CapaModule(XModule):
# Figure out if we should move these to capa_problem?
def get_problem(self, get):
''' Return results of get_problem_html, as a simple dict for json-ing.
- { 'html': }
+{ 'html': }
- Used if we want to reconfirm we have the right thing e.g. after
- several AJAX calls.
- '''
+Used if we want to reconfirm we have the right thing e.g. after
+several AJAX calls.
+'''
return {'html': self.get_problem_html(encapsulate=False)}
@staticmethod
def make_dict_of_responses(get):
'''Make dictionary of student responses (aka "answers")
- get is POST dictionary.
- '''
+get is POST dictionary.
+'''
answers = dict()
for key in get:
# e.g. input_resistor_1 ==> resistor_1
@@ -500,11 +500,11 @@ class CapaModule(XModule):
def check_problem(self, get):
''' Checks whether answers to a problem are correct, and
- returns a map of correct/incorrect answers:
+returns a map of correct/incorrect answers:
- {'success' : bool,
- 'contents' : html}
- '''
+{'success' : bool,
+'contents' : html}
+'''
event_info = dict()
event_info['state'] = self.lcp.get_state()
event_info['problem_id'] = self.location.url()
@@ -527,11 +527,11 @@ class CapaModule(XModule):
# Problem queued. Students must wait a specified waittime before they are allowed to submit
if self.lcp.is_queued():
current_time = datetime.datetime.now()
- prev_submit_time = self.lcp.get_recentmost_queuetime()
+ prev_submit_time = self.lcp.get_recentmost_queuetime()
waittime_between_requests = self.system.xqueue['waittime']
if (current_time-prev_submit_time).total_seconds() < waittime_between_requests:
msg = 'You must wait at least %d seconds between submissions' % waittime_between_requests
- return {'success': msg, 'html': ''} # Prompts a modal dialog in ajax callback
+ return {'success': msg, 'html': ''} # Prompts a modal dialog in ajax callback
try:
old_state = self.lcp.get_state()
@@ -540,13 +540,13 @@ class CapaModule(XModule):
except StudentInputError as inst:
# TODO (vshnayder): why is this line here?
#self.lcp = LoncapaProblem(self.definition['data'],
- # id=lcp_id, state=old_state, system=self.system)
+ # id=lcp_id, state=old_state, system=self.system)
log.exception("StudentInputError in capa_module:problem_check")
return {'success': inst.message}
except Exception, err:
# TODO: why is this line here?
#self.lcp = LoncapaProblem(self.definition['data'],
- # id=lcp_id, state=old_state, system=self.system)
+ # id=lcp_id, state=old_state, system=self.system)
if self.system.DEBUG:
msg = "Error checking problem: " + str(err)
msg += '\nTraceback:\n' + traceback.format_exc()
@@ -564,99 +564,99 @@ class CapaModule(XModule):
success = 'incorrect'
# NOTE: We are logging both full grading and queued-grading submissions. In the latter,
- # 'success' will always be incorrect
+ # 'success' will always be incorrect
event_info['correct_map'] = correct_map.get_dict()
event_info['success'] = success
- event_info['attempts'] = self.attempts
- self.system.track_function('save_problem_check', event_info)
+event_info['attempts'] = self.attempts
+self.system.track_function('save_problem_check', event_info)
- if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback
- self.system.psychometrics_handler(self.get_instance_state())
+if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback
+ self.system.psychometrics_handler(self.get_instance_state())
- # render problem into HTML
- html = self.get_problem_html(encapsulate=False)
+# render problem into HTML
+html = self.get_problem_html(encapsulate=False)
- return {'success': success,
- 'contents': html,
- }
+return {'success': success,
+ 'contents': html,
+ }
- def save_problem(self, get):
- '''
- Save the passed in answers.
- Returns a dict { 'success' : bool, ['error' : error-msg]},
- with the error key only present if success is False.
- '''
- event_info = dict()
- event_info['state'] = self.lcp.get_state()
- event_info['problem_id'] = self.location.url()
+def save_problem(self, get):
+ '''
+ Save the passed in answers.
+ Returns a dict { 'success' : bool, ['error' : error-msg]},
+ with the error key only present if success is False.
+ '''
+ event_info = dict()
+ event_info['state'] = self.lcp.get_state()
+ event_info['problem_id'] = self.location.url()
- answers = self.make_dict_of_responses(get)
- event_info['answers'] = answers
+ answers = self.make_dict_of_responses(get)
+ event_info['answers'] = answers
- # Too late. Cannot submit
- if self.closed():
- event_info['failure'] = 'closed'
- self.system.track_function('save_problem_fail', event_info)
- return {'success': False,
- 'error': "Problem is closed"}
-
- # Problem submitted. Student should reset before saving
- # again.
- if self.lcp.done and self.rerandomize == "always":
- event_info['failure'] = 'done'
- self.system.track_function('save_problem_fail', event_info)
- return {'success': False,
- 'error': "Problem needs to be reset prior to save."}
-
- self.lcp.student_answers = answers
-
- # TODO: should this be save_problem_fail? Looks like success to me...
+ # Too late. Cannot submit
+ if self.closed():
+ event_info['failure'] = 'closed'
self.system.track_function('save_problem_fail', event_info)
- return {'success': True}
+ return {'success': False,
+ 'error': "Problem is closed"}
- def reset_problem(self, get):
- ''' Changes problem state to unfinished -- removes student answers,
- and causes problem to rerender itself.
+ # Problem submitted. Student should reset before saving
+ # again.
+ if self.lcp.done and self.rerandomize == "always":
+ event_info['failure'] = 'done'
+ self.system.track_function('save_problem_fail', event_info)
+ return {'success': False,
+ 'error': "Problem needs to be reset prior to save."}
- Returns problem html as { 'html' : html-string }.
- '''
- event_info = dict()
- event_info['old_state'] = self.lcp.get_state()
- event_info['problem_id'] = self.location.url()
+ self.lcp.student_answers = answers
- if self.closed():
- event_info['failure'] = 'closed'
- self.system.track_function('reset_problem_fail', event_info)
- return {'success': False,
- 'error': "Problem is closed"}
+ # TODO: should this be save_problem_fail? Looks like success to me...
+ self.system.track_function('save_problem_fail', event_info)
+ return {'success': True}
- if not self.lcp.done:
- event_info['failure'] = 'not_done'
- self.system.track_function('reset_problem_fail', event_info)
- return {'success': False,
- 'error': "Refresh the page and make an attempt before resetting."}
+def reset_problem(self, get):
+ ''' Changes problem state to unfinished -- removes student answers,
+ and causes problem to rerender itself.
- self.lcp.do_reset()
- if self.rerandomize in ["always", "onreset"]:
- # reset random number generator seed (note the self.lcp.get_state()
- # in next line)
- self.lcp.seed = None
+ Returns problem html as { 'html' : html-string }.
+ '''
+ event_info = dict()
+ event_info['old_state'] = self.lcp.get_state()
+ event_info['problem_id'] = self.location.url()
- self.lcp = LoncapaProblem(self.definition['data'],
- self.location.html_id(), self.lcp.get_state(),
- system=self.system)
+ if self.closed():
+ event_info['failure'] = 'closed'
+ self.system.track_function('reset_problem_fail', event_info)
+ return {'success': False,
+ 'error': "Problem is closed"}
- event_info['new_state'] = self.lcp.get_state()
- self.system.track_function('reset_problem', event_info)
+ if not self.lcp.done:
+ event_info['failure'] = 'not_done'
+ self.system.track_function('reset_problem_fail', event_info)
+ return {'success': False,
+ 'error': "Refresh the page and make an attempt before resetting."}
- return {'html': self.get_problem_html(encapsulate=False)}
+ self.lcp.do_reset()
+ if self.rerandomize in ["always", "onreset"]:
+ # reset random number generator seed (note the self.lcp.get_state()
+ # in next line)
+ self.lcp.seed = None
+
+ self.lcp = LoncapaProblem(self.definition['data'],
+ self.location.html_id(), self.lcp.get_state(),
+ system=self.system)
+
+ event_info['new_state'] = self.lcp.get_state()
+ self.system.track_function('reset_problem', event_info)
+
+ return {'html': self.get_problem_html(encapsulate=False)}
class CapaDescriptor(RawDescriptor):
"""
- Module implementing problems in the LON-CAPA format,
- as implemented by capa.capa_problem
- """
+Module implementing problems in the LON-CAPA format,
+as implemented by capa.capa_problem
+"""
module_class = CapaModule
@@ -665,7 +665,7 @@ class CapaDescriptor(RawDescriptor):
template_dir_name = 'problem'
# Capa modules have some additional metadata:
- # TODO (vshnayder): do problems have any other metadata? Do they
+ # TODO (vshnayder): do problems have any other metadata? Do they
# actually use type and points?
metadata_attributes = RawDescriptor.metadata_attributes + ('type', 'points')
@@ -677,13 +677,13 @@ class CapaDescriptor(RawDescriptor):
return [
'problems/' + path[8:],
path[8:],
- ]
-
+ ]
+
def __init__(self, *args, **kwargs):
super(CapaDescriptor, self).__init__(*args, **kwargs)
-
+
weight_string = self.metadata.get('weight', None)
if weight_string:
self.weight = float(weight_string)
else:
- self.weight = None
+ self.weight = None
\ No newline at end of file
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index df69213281..4473b7d430 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -108,6 +108,341 @@ class SelfAssessmentModule(XModule):
'ajax_url': self.system.ajax_url,
})
+ def get_problem_html(self, encapsulate=True):
+ '''Return html for the problem. Adds check, reset, save buttons
+ as necessary based on the problem config and state.'''
+
+ try:
+ html = self.lcp.get_html()
+ except Exception, err:
+ log.exception(err)
+
+ # TODO (vshnayder): another switch on DEBUG.
+ if self.system.DEBUG:
+ msg = (
+ '[courseware.capa.capa_module] '
+ 'Failed to generate HTML for problem %s' %
+ (self.location.url()))
+ msg += '
Error:
%s
' % str(err).replace('<', '<')
+ msg += '
%s
' % traceback.format_exc().replace('<', '<')
+ html = msg
+ else:
+ # We're in non-debug mode, and possibly even in production. We want
+ # to avoid bricking of problem as much as possible
+
+ # Presumably, student submission has corrupted LoncapaProblem HTML.
+ # First, pull down all student answers
+ student_answers = self.lcp.student_answers
+ answer_ids = student_answers.keys()
+
+ # Some inputtypes, such as dynamath, have additional "hidden" state that
+ # is not exposed to the student. Keep those hidden
+ # TODO: Use regex, e.g. 'dynamath' is suffix at end of answer_id
+ hidden_state_keywords = ['dynamath']
+ for answer_id in answer_ids:
+ for hidden_state_keyword in hidden_state_keywords:
+ if answer_id.find(hidden_state_keyword) >= 0:
+ student_answers.pop(answer_id)
+
+ # Next, generate a fresh LoncapaProblem
+ self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(),
+ state=None, # Tabula rasa
+ seed=self.seed, system=self.system)
+
+ # Prepend a scary warning to the student
+ warning = '
'\
+ '
Warning: The problem has been reset to its initial state!
'\
+ 'The problem\'s state was corrupted by an invalid submission. '\
+ 'The submission consisted of:'\
+ '
'
+ for student_answer in student_answers.values():
+ if student_answer != '':
+ warning += '
' + cgi.escape(student_answer) + '
'
+ warning += '
'\
+ 'If this error persists, please contact the course staff.'\
+ '
'
+
+ html = warning
+ try:
+ html += self.lcp.get_html()
+ except Exception, err: # Couldn't do it. Give up
+ log.exception(err)
+ raise
+
+ content = {'name': self.display_name,
+ 'html': html,
+ 'weight': self.descriptor.weight,
+ }
+
+ # We using strings as truthy values, because the terminology of the
+ # check button is context-specific.
+
+ # Put a "Check" button if unlimited attempts or still some left
+ if self.max_attempts is None or self.attempts < self.max_attempts-1:
+ check_button = "Check"
+ else:
+ # Will be final check so let user know that
+ check_button = "Final Check"
+
+ reset_button = True
+ save_button = True
+
+ # If we're after deadline, or user has exhausted attempts,
+ # question is read-only.
+ if self.closed():
+ check_button = False
+ reset_button = False
+ save_button = False
+
+ # User submitted a problem, and hasn't reset. We don't want
+ # more submissions.
+ if self.lcp.done and self.rerandomize == "always":
+ check_button = False
+ save_button = False
+
+ # Only show the reset button if pressing it will show different values
+ if self.rerandomize not in ["always", "onreset"]:
+ reset_button = False
+
+ # User hasn't submitted an answer yet -- we don't want resets
+ if not self.lcp.done:
+ reset_button = False
+
+ # We may not need a "save" button if infinite number of attempts and
+ # non-randomized. The problem author can force it. It's a bit weird for
+ # randomization to control this; should perhaps be cleaned up.
+ if (self.force_save_button == "false") and (self.max_attempts is None and self.rerandomize != "always"):
+ save_button = False
+
+ context = {'problem': content,
+ 'id': self.id,
+ 'check_button': check_button,
+ 'reset_button': reset_button,
+ 'save_button': save_button,
+ 'answer_available': self.answer_available(),
+ 'ajax_url': self.system.ajax_url,
+ 'attempts_used': self.attempts,
+ 'attempts_allowed': self.max_attempts,
+ 'progress': self.get_progress(),
+ }
+
+ html = self.system.render_template('problem.html', context)
+ if encapsulate:
+ html = '
'.format(
+ id=self.location.html_id(), ajax_url=self.system.ajax_url) + html + "
"
+
+ # cdodge: OK, we have to do two rounds of url reference subsitutions
+ # one which uses the 'asset library' that is served by the contentstore and the
+ # more global /static/ filesystem based static content.
+ # NOTE: rewrite_content_links is defined in XModule
+ # This is a bit unfortunate and I'm sure we'll try to considate this into
+ # a one step process.
+ html = rewrite_links(html, self.rewrite_content_links)
+
+ # now do the substitutions which are filesystem based, e.g. '/static/' prefixes
+ return self.system.replace_urls(html, self.metadata['data_dir'])
+
+ def handle_ajax(self, dispatch, get):
+ '''
+ This is called by courseware.module_render, to handle an AJAX call.
+ "get" is request.POST.
+
+ Returns a json dictionary:
+ { 'progress_changed' : True/False,
+ 'progress' : 'none'/'in_progress'/'done',
+ }
+ '''
+ handlers = {
+ 'problem_get': self.get_problem,
+ 'problem_check': self.check_problem,
+ 'problem_reset': self.reset_problem,
+ 'problem_save': self.save_problem,
+ 'problem_show': self.get_answer,
+ 'score_update': self.update_score,
+ }
+
+ if dispatch not in handlers:
+ return 'Error'
+
+ before = self.get_progress()
+ d = handlers[dispatch](get)
+ after = self.get_progress()
+ d.update({
+ 'progress_changed': after != before,
+ 'progress_status': Progress.to_js_status_str(after),
+ })
+ return json.dumps(d, cls=ComplexEncoder)
+
+ def closed(self):
+ ''' Is the student still allowed to submit answers? '''
+ if self.attempts == self.max_attempts:
+ return True
+ if self.close_date is not None and datetime.datetime.utcnow() > self.close_date:
+ return True
+
+ return False
+
+ def answer_available(self):
+ ''' Is the user allowed to see an answer?
+ '''
+ if self.show_answer == '':
+ return False
+
+ if self.show_answer == "never":
+ return False
+
+ # Admins can see the answer, unless the problem explicitly prevents it
+ if self.system.user_is_staff:
+ return True
+
+ if self.show_answer == 'attempted':
+ return self.attempts > 0
+
+ if self.show_answer == 'answered':
+ return self.lcp.done
+
+ if self.show_answer == 'closed':
+ return self.closed()
+
+ if self.show_answer == 'always':
+ return True
+
+ return False
+
+ # Figure out if we should move these to capa_problem?
+ def get_problem(self, get):
+ ''' Return results of get_problem_html, as a simple dict for json-ing.
+ { 'html': }
+
+ Used if we want to reconfirm we have the right thing e.g. after
+ several AJAX calls.
+ '''
+ return {'html': self.get_problem_html(encapsulate=False)}
+
+ @staticmethod
+ def make_dict_of_responses(get):
+ '''Make dictionary of student responses (aka "answers")
+ get is POST dictionary.
+ '''
+ answers = dict()
+ for key in get:
+ # e.g. input_resistor_1 ==> resistor_1
+ _, _, name = key.partition('_')
+
+ # This allows for answers which require more than one value for
+ # the same form input (e.g. checkbox inputs). The convention is that
+ # if the name ends with '[]' (which looks like an array), then the
+ # answer will be an array.
+ if not name.endswith('[]'):
+ answers[name] = get[key]
+ else:
+ name = name[:-2]
+ answers[name] = get.getlist(key)
+
+ return answers
+
+ def check_problem(self, get):
+ ''' Checks whether answers to a problem are correct, and
+ returns a map of correct/incorrect answers:
+
+ {'success' : bool,
+ 'contents' : html}
+ '''
+ event_info = dict()
+ event_info['state'] = self.lcp.get_state()
+ event_info['problem_id'] = self.location.url()
+
+ answers = self.make_dict_of_responses(get)
+ event_info['answers'] = convert_files_to_filenames(answers)
+
+ parsed_answer=False
+ if(answer[answers.keys()[0]]=="True"):
+ parsed_answer=True
+
+ # Too late. Cannot submit
+ if self.closed():
+ event_info['failure'] = 'closed'
+ self.system.track_function('save_problem_check_fail', event_info)
+ raise NotFoundError('Problem is closed')
+
+ try:
+ old_state = self.lcp.get_state()
+ lcp_id = self.lcp.problem_id
+ correct_map = self.lcp.grade_answers(answers)
+ correct_map.set(correctness=parsed_answer)
+ except StudentInputError as inst:
+ # TODO (vshnayder): why is this line here?
+ #self.lcp = LoncapaProblem(self.definition['data'],
+ # id=lcp_id, state=old_state, system=self.system)
+ log.exception("StudentInputError in capa_module:problem_check")
+ return {'success': inst.message}
+ except Exception, err:
+ # TODO: why is this line here?
+ #self.lcp = LoncapaProblem(self.definition['data'],
+ # id=lcp_id, state=old_state, system=self.system)
+ if self.system.DEBUG:
+ msg = "Error checking problem: " + str(err)
+ msg += '\nTraceback:\n' + traceback.format_exc()
+ return {'success': msg}
+ log.exception("Error in capa_module problem checking")
+ raise Exception("error in capa_module")
+
+ self.attempts = self.attempts + 1
+ self.lcp.done = True
+
+ # success = correct if ALL questions in this problem are correct
+ success = 'correct'
+ for answer_id in correct_map:
+ if not correct_map.is_correct(answer_id):
+ success = 'incorrect'
+
+ # NOTE: We are logging both full grading and queued-grading submissions. In the latter,
+ # 'success' will always be incorrect
+ event_info['correct_map'] = correct_map.get_dict()
+ event_info['success'] = success
+ event_info['attempts'] = self.attempts
+ self.system.track_function('save_problem_check', event_info)
+
+ # render problem into HTML
+ html = self.get_problem_html(encapsulate=False)
+
+ return {'success': success,
+ 'contents': html,
+ }
+
+ def save_problem(self, get):
+ '''
+ Save the passed in answers.
+ Returns a dict { 'success' : bool, ['error' : error-msg]},
+ with the error key only present if success is False.
+ '''
+ event_info = dict()
+ event_info['state'] = self.lcp.get_state()
+ event_info['problem_id'] = self.location.url()
+
+ answers = self.make_dict_of_responses(get)
+ event_info['answers'] = answers
+
+ # Too late. Cannot submit
+ if self.closed():
+ event_info['failure'] = 'closed'
+ self.system.track_function('save_problem_fail', event_info)
+ return {'success': False,
+ 'error': "Problem is closed"}
+
+ # Problem submitted. Student should reset before saving
+ # again.
+ if self.lcp.done and self.rerandomize == "always":
+ event_info['failure'] = 'done'
+ self.system.track_function('save_problem_fail', event_info)
+ return {'success': False,
+ 'error': "Problem needs to be reset prior to save."}
+
+ self.lcp.student_answers = answers
+
+ # TODO: should this be save_problem_fail? Looks like success to me...
+ self.system.track_function('save_problem_fail', event_info)
+ return {'success': True}
class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
From 32a8ed7be32c1451a3fc5b591f5ee86585162d96 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Wed, 31 Oct 2012 16:54:00 -0400
Subject: [PATCH 009/121] adding in self assessment grader
---
common/lib/xmodule/xmodule/capa_module.py | 116 ++++++------
.../js/src/selfassessment/display.coffee | 24 ---
.../xmodule/xmodule/self_assessment_module.py | 168 ++++--------------
3 files changed, 91 insertions(+), 217 deletions(-)
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index 31106a4aa8..76158093b6 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -29,10 +29,10 @@ TIMEDELTA_REGEX = re.compile(r'^((?P\d+?) day(?:s?))?(\s)?((?P\d+?)
def only_one(lst, default="", process=lambda x: x):
"""
-If lst is empty, returns default
-If lst has a single element, applies process to that element and returns it
-Otherwise, raises an exeception
-"""
+ If lst is empty, returns default
+ If lst has a single element, applies process to that element and returns it
+ Otherwise, raises an exeception
+ """
if len(lst) == 0:
return default
elif len(lst) == 1:
@@ -43,14 +43,14 @@ Otherwise, raises an exeception
def parse_timedelta(time_str):
"""
-time_str: A string with the following components:
- day[s] (optional)
- hour[s] (optional)
- minute[s] (optional)
- second[s] (optional)
+ time_str: A string with the following components:
+ day[s] (optional)
+ hour[s] (optional)
+ minute[s] (optional)
+ second[s] (optional)
-Returns a datetime.timedelta parsed from the string
-"""
+ Returns a datetime.timedelta parsed from the string
+ """
parts = TIMEDELTA_REGEX.match(time_str)
if not parts:
return
@@ -71,9 +71,9 @@ class ComplexEncoder(json.JSONEncoder):
class CapaModule(XModule):
'''
-An XModule implementing LonCapa format problems, implemented by way of
-capa.capa_problem.LoncapaProblem
-'''
+ An XModule implementing LonCapa format problems, implemented by way of
+ capa.capa_problem.LoncapaProblem
+ '''
icon_class = 'problem'
js = {'coffee': [resource_string(__name__, 'js/src/capa/display.coffee'),
@@ -175,9 +175,9 @@ capa.capa_problem.LoncapaProblem
@property
def rerandomize(self):
"""
-Property accessor that returns self.metadata['rerandomize'] in a
-canonical form
-"""
+ Property accessor that returns self.metadata['rerandomize'] in a
+ canonical form
+ """
rerandomize = self.metadata.get('rerandomize', 'always')
if rerandomize in ("", "always", "true"):
return "always"
@@ -203,7 +203,7 @@ canonical form
def get_progress(self):
''' For now, just return score / max_score
-'''
+ '''
d = self.get_score()
score = d['score']
total = d['total']
@@ -224,7 +224,7 @@ canonical form
def get_problem_html(self, encapsulate=True):
'''Return html for the problem. Adds check, reset, save buttons
-as necessary based on the problem config and state.'''
+ as necessary based on the problem config and state.'''
try:
html = self.lcp.get_html()
@@ -358,14 +358,14 @@ as necessary based on the problem config and state.'''
def handle_ajax(self, dispatch, get):
'''
-This is called by courseware.module_render, to handle an AJAX call.
-"get" is request.POST.
+ This is called by courseware.module_render, to handle an AJAX call.
+ "get" is request.POST.
-Returns a json dictionary:
-{ 'progress_changed' : True/False,
-'progress' : 'none'/'in_progress'/'done',
- }
-'''
+ Returns a json dictionary:
+ { 'progress_changed' : True/False,
+ 'progress' : 'none'/'in_progress'/'done',
+ }
+ '''
handlers = {
'problem_get': self.get_problem,
'problem_check': self.check_problem,
@@ -398,7 +398,7 @@ Returns a json dictionary:
def answer_available(self):
''' Is the user allowed to see an answer?
-'''
+ '''
if self.show_answer == '':
return False
@@ -425,14 +425,14 @@ Returns a json dictionary:
def update_score(self, get):
"""
-Delivers grading response (e.g. from asynchronous code checking) to
-the capa problem, so its score can be updated
+ Delivers grading response (e.g. from asynchronous code checking) to
+ the capa problem, so its score can be updated
-'get' must have a field 'response' which is a string that contains the
-grader's response
+ 'get' must have a field 'response' which is a string that contains the
+ grader's response
-No ajax return is needed. Return empty dict.
-"""
+ No ajax return is needed. Return empty dict.
+ """
queuekey = get['queuekey']
score_msg = get['xqueue_body']
self.lcp.update_score(score_msg, queuekey)
@@ -441,10 +441,10 @@ No ajax return is needed. Return empty dict.
def get_answer(self, get):
'''
-For the "show answer" button.
+ For the "show answer" button.
-Returns the answers: {'answers' : answers}
-'''
+ Returns the answers: {'answers' : answers}
+ '''
event_info = dict()
event_info['problem_id'] = self.location.url()
self.system.track_function('show_answer', event_info)
@@ -469,18 +469,18 @@ Returns the answers: {'answers' : answers}
# Figure out if we should move these to capa_problem?
def get_problem(self, get):
''' Return results of get_problem_html, as a simple dict for json-ing.
-{ 'html': }
+ { 'html': }
-Used if we want to reconfirm we have the right thing e.g. after
-several AJAX calls.
-'''
+ Used if we want to reconfirm we have the right thing e.g. after
+ several AJAX calls.
+ '''
return {'html': self.get_problem_html(encapsulate=False)}
@staticmethod
def make_dict_of_responses(get):
'''Make dictionary of student responses (aka "answers")
-get is POST dictionary.
-'''
+ get is POST dictionary.
+ '''
answers = dict()
for key in get:
# e.g. input_resistor_1 ==> resistor_1
@@ -500,11 +500,11 @@ get is POST dictionary.
def check_problem(self, get):
''' Checks whether answers to a problem are correct, and
-returns a map of correct/incorrect answers:
+ returns a map of correct/incorrect answers:
-{'success' : bool,
-'contents' : html}
-'''
+ {'success' : bool,
+ 'contents' : html}
+ '''
event_info = dict()
event_info['state'] = self.lcp.get_state()
event_info['problem_id'] = self.location.url()
@@ -567,18 +567,18 @@ returns a map of correct/incorrect answers:
# 'success' will always be incorrect
event_info['correct_map'] = correct_map.get_dict()
event_info['success'] = success
-event_info['attempts'] = self.attempts
-self.system.track_function('save_problem_check', event_info)
+ event_info['attempts'] = self.attempts
+ self.system.track_function('save_problem_check', event_info)
-if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback
- self.system.psychometrics_handler(self.get_instance_state())
+ if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback
+ self.system.psychometrics_handler(self.get_instance_state())
-# render problem into HTML
-html = self.get_problem_html(encapsulate=False)
+ # render problem into HTML
+ html = self.get_problem_html(encapsulate=False)
-return {'success': success,
- 'contents': html,
- }
+ return {'success': success,
+ 'contents': html,
+ }
def save_problem(self, get):
'''
@@ -654,9 +654,9 @@ def reset_problem(self, get):
class CapaDescriptor(RawDescriptor):
"""
-Module implementing problems in the LON-CAPA format,
-as implemented by capa.capa_problem
-"""
+ Module implementing problems in the LON-CAPA format,
+ as implemented by capa.capa_problem
+ """
module_class = CapaModule
diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
index da91c44af7..5e16182553 100644
--- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
@@ -125,30 +125,6 @@ class @Problem
@gentle_alert saveMessage
@updateProgress response
- refreshMath: (event, element) =>
- element = event.target unless element
- elid = element.id.replace(/^input_/,'')
- target = "display_" + elid
-
- # MathJax preprocessor is loaded by 'setupInputTypes'
- preprocessor_tag = "inputtype_" + elid
- mathjax_preprocessor = @inputtypeDisplays[preprocessor_tag]
-
- if jax = MathJax.Hub.getAllJax(target)[0]
- eqn = $(element).val()
- if mathjax_preprocessor
- eqn = mathjax_preprocessor(eqn)
- MathJax.Hub.Queue(['Text', jax, eqn], [@updateMathML, jax, element])
-
- return # Explicit return for CoffeeScript
-
- updateMathML: (jax, element) =>
- try
- $("##{element.id}_dynamath").val(jax.root.toMathML '')
- catch exception
- throw exception unless exception.restart
- MathJax.Callback.After [@refreshMath, jax], exception.restart
-
refreshAnswers: =>
@$('input.schematic').each (index, element) ->
element.schematic.update_value()
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index 4473b7d430..b6e8a667b4 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -57,9 +57,10 @@ class SelfAssessmentModule(XModule):
dom2 = etree.fromstring(definition['data'])
self.attempts = 0
- self.max_attempts = None
+ self.max_attempts = 1
self.max_attempts = self.metadata.get('attempts', None)
+
if self.max_attempts is not None:
self.max_attempts = int(self.max_attempts)
@@ -70,7 +71,7 @@ class SelfAssessmentModule(XModule):
self.name = only_one(dom2.xpath('/problem/@name'))
- self.rubric=etree.tostring(only_one(dom2.xpath("/rubric")))
+ self.rubric=etree.tostring(only_one(dom2.xpath("/rubric/html")))
self.problem=etree.tostring(only_one(dom2.xpath("/problem")))
self.lcp = LoncapaProblem(self.problem, self.location.html_id(),
@@ -255,10 +256,7 @@ class SelfAssessmentModule(XModule):
handlers = {
'problem_get': self.get_problem,
'problem_check': self.check_problem,
- 'problem_reset': self.reset_problem,
'problem_save': self.save_problem,
- 'problem_show': self.get_answer,
- 'score_update': self.update_score,
}
if dispatch not in handlers:
@@ -274,7 +272,7 @@ class SelfAssessmentModule(XModule):
return json.dumps(d, cls=ComplexEncoder)
def closed(self):
- ''' Is the student still allowed to submit answers? '''
+ ''' Is the student still allowed to submit answers? '''
if self.attempts == self.max_attempts:
return True
if self.close_date is not None and datetime.datetime.utcnow() > self.close_date:
@@ -319,9 +317,10 @@ class SelfAssessmentModule(XModule):
'''
return {'html': self.get_problem_html(encapsulate=False)}
- @staticmethod
+ @staticmethod
def make_dict_of_responses(get):
- '''Make dictionary of student responses (aka "answers")
+ '''
+ Make dictionary of student responses (aka "answers")
get is POST dictionary.
'''
answers = dict()
@@ -355,9 +354,9 @@ class SelfAssessmentModule(XModule):
answers = self.make_dict_of_responses(get)
event_info['answers'] = convert_files_to_filenames(answers)
- parsed_answer=False
- if(answer[answers.keys()[0]]=="True"):
- parsed_answer=True
+ parsed_answer="incorrect"
+ if(answer[answers.keys()[1]]=="Correct"):
+ parsed_answer="correct"
# Too late. Cannot submit
if self.closed():
@@ -445,137 +444,36 @@ class SelfAssessmentModule(XModule):
return {'success': True}
-class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
+class SelfAssessmentDescriptor(RawDescriptor):
"""
Module for putting raw html in a course
"""
- mako_template = "widgets/html-edit.html"
- module_class = HtmlModule
- filename_extension = "xml"
- template_dir_name = "html"
- stores_state=True
- has_score=True
+ module_class = SelfAssessmentModule
- js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/edit.coffee')]}
- js_module_name = "HTMLEditingDescriptor"
+ stores_state = True
+ has_score = True
+ template_dir_name = 'selfassessment'
- # VS[compat] TODO (cpennington): Delete this method once all fall 2012 course
- # are being edited in the cms
+ # Capa modules have some additional metadata:
+ # TODO (vshnayder): do problems have any other metadata? Do they
+ # actually use type and points?
+ metadata_attributes = RawDescriptor.metadata_attributes + ('type', 'points')
+
+ # VS[compat]
+ # TODO (cpennington): Delete this method once all fall 2012 course are being
+ # edited in the cms
@classmethod
def backcompat_paths(cls, path):
- if path.endswith('.html.xml'):
- path = path[:-9] + '.html' # backcompat--look for html instead of xml
- if path.endswith('.html.html'):
- path = path[:-5] # some people like to include .html in filenames..
- candidates = []
- while os.sep in path:
- candidates.append(path)
- _, _, path = path.partition(os.sep)
+ return [
+ 'problems/' + path[8:],
+ path[8:],
+ ]
- # also look for .html versions instead of .xml
- nc = []
- for candidate in candidates:
- if candidate.endswith('.xml'):
- nc.append(candidate[:-4] + '.html')
- return candidates + nc
+ def __init__(self, *args, **kwargs):
+ super(CapaDescriptor, self).__init__(*args, **kwargs)
- # NOTE: html descriptors are special. We do not want to parse and
- # export them ourselves, because that can break things (e.g. lxml
- # adds body tags when it exports, but they should just be html
- # snippets that will be included in the middle of pages.
-
- @classmethod
- def load_definition(cls, xml_object, system, location):
- '''Load a descriptor from the specified xml_object:
-
- If there is a filename attribute, load it as a string, and
- log a warning if it is not parseable by etree.HTMLParser.
-
- If there is not a filename attribute, the definition is the body
- of the xml_object, without the root tag (do not want in the
- middle of a page)
- '''
- filename = xml_object.get('filename')
- if filename is None:
- definition_xml = copy.deepcopy(xml_object)
- cls.clean_metadata_from_xml(definition_xml)
- return {'data': stringify_children(definition_xml)}
+ weight_string = self.metadata.get('weight', None)
+ if weight_string:
+ self.weight = float(weight_string)
else:
- # html is special. cls.filename_extension is 'xml', but
- # if 'filename' is in the definition, that means to load
- # from .html
- # 'filename' in html pointers is a relative path
- # (not same as 'html/blah.html' when the pointer is in a directory itself)
- pointer_path = "{category}/{url_path}".format(category='html',
- url_path=name_to_pathname(location.name))
- base = path(pointer_path).dirname()
- #log.debug("base = {0}, base.dirname={1}, filename={2}".format(base, base.dirname(), filename))
- filepath = "{base}/{name}.html".format(base=base, name=filename)
- #log.debug("looking for html file for {0} at {1}".format(location, filepath))
-
-
-
- # VS[compat]
- # TODO (cpennington): If the file doesn't exist at the right path,
- # give the class a chance to fix it up. The file will be written out
- # again in the correct format. This should go away once the CMS is
- # online and has imported all current (fall 2012) courses from xml
- if not system.resources_fs.exists(filepath):
- candidates = cls.backcompat_paths(filepath)
- #log.debug("candidates = {0}".format(candidates))
- for candidate in candidates:
- if system.resources_fs.exists(candidate):
- filepath = candidate
- break
-
- try:
- with system.resources_fs.open(filepath) as file:
- html = file.read()
- # Log a warning if we can't parse the file, but don't error
- if not check_html(html):
- msg = "Couldn't parse html in {0}.".format(filepath)
- log.warning(msg)
- system.error_tracker("Warning: " + msg)
-
- definition = {'data': html}
-
- # TODO (ichuang): remove this after migration
- # for Fall 2012 LMS migration: keep filename (and unmangled filename)
- definition['filename'] = [ filepath, filename ]
-
- return definition
-
- except (ResourceNotFoundError) as err:
- msg = 'Unable to load file contents at path {0}: {1} '.format(
- filepath, err)
- # add more info and re-raise
- raise Exception(msg), None, sys.exc_info()[2]
-
- # TODO (vshnayder): make export put things in the right places.
-
- def definition_to_xml(self, resource_fs):
- '''If the contents are valid xml, write them to filename.xml. Otherwise,
- write just to filename.xml, and the html
- string to filename.html.
- '''
- try:
- return etree.fromstring(self.definition['data'])
- except etree.XMLSyntaxError:
- pass
-
- # Not proper format. Write html to file, return an empty tag
- pathname = name_to_pathname(self.url_name)
- pathdir = path(pathname).dirname()
- filepath = u'{category}/{pathname}.html'.format(category=self.category,
- pathname=pathname)
-
- resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True)
- with resource_fs.open(filepath, 'w') as file:
- file.write(self.definition['data'])
-
- # write out the relative name
- relname = path(pathname).basename()
-
- elt = etree.Element('html')
- elt.set("filename", relname)
- return elt
+ self.weight = None
\ No newline at end of file
From 562dee067c307edcabda4d5bbb79aefe5687d79c Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Wed, 31 Oct 2012 17:10:51 -0400
Subject: [PATCH 010/121] rolling back self assessment module
---
common/lib/xmodule/xmodule/self_assessment_module.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index b6e8a667b4..0c31a48b1e 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -452,7 +452,7 @@ class SelfAssessmentDescriptor(RawDescriptor):
stores_state = True
has_score = True
- template_dir_name = 'selfassessment'
+ template_dir_name = 'problem'
# Capa modules have some additional metadata:
# TODO (vshnayder): do problems have any other metadata? Do they
@@ -465,7 +465,7 @@ class SelfAssessmentDescriptor(RawDescriptor):
@classmethod
def backcompat_paths(cls, path):
return [
- 'problems/' + path[8:],
+ 'problem/' + path[8:],
path[8:],
]
From 1bf548af2859bf784e47435d1d26ba216c8464a0 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Wed, 31 Oct 2012 17:41:46 -0400
Subject: [PATCH 011/121] basic parsing for problem and rubric
---
.../xmodule/xmodule/self_assessment_module.py | 563 +++++-------------
1 file changed, 145 insertions(+), 418 deletions(-)
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index 0c31a48b1e..ad42090eea 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -1,28 +1,26 @@
-import cgi
-import datetime
-import dateutil
-import dateutil.parser
-import json
+import copy
+from fs.errors import ResourceNotFoundError
import logging
-import traceback
-import re
+import os
import sys
-
-from datetime import timedelta
from lxml import etree
from lxml.html import rewrite_links
-from pkg_resources import resource_string
+from path import path
-from capa.capa_problem import LoncapaProblem
-from capa.responsetypes import StudentInputError
-from capa.util import convert_files_to_filenames
-from progress import Progress
-from xmodule.x_module import XModule
-from xmodule.raw_module import RawDescriptor
-from xmodule.exceptions import NotFoundError
+from .x_module import XModule
+from pkg_resources import resource_string
+from .xml_module import XmlDescriptor, name_to_pathname
+from .editing_module import EditingDescriptor
+from .stringify import stringify_children
+from .html_checker import check_html
+from xmodule.modulestore import Location
+
+from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent
log = logging.getLogger("mitx.courseware")
+
+
def only_one(lst, default="", process=lambda x: x):
"""
If lst is empty, returns default
@@ -54,426 +52,155 @@ class SelfAssessmentModule(XModule):
XModule.__init__(self, system, location, definition, descriptor,
instance_state, shared_state, **kwargs)
- dom2 = etree.fromstring(definition['data'])
+ dom2=etree.fromstring("" + self.definition['data'] + "")
+ rubric=''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))])
+ problem=''.join([etree.tostring(child) for child in only_one(dom2.xpath('problem'))])
- self.attempts = 0
- self.max_attempts = 1
+ #print(etree.tostring(rubric))
+ #print(etree.tostring(problem))
- self.max_attempts = self.metadata.get('attempts', None)
+ self.html = etree.tostring(problem)
- if self.max_attempts is not None:
- self.max_attempts = int(self.max_attempts)
+ #self.html=self.definition['data']
- if instance_state is not None:
- instance_state = json.loads(instance_state)
- if instance_state is not None and 'attempts' in instance_state:
- self.attempts = instance_state['attempts']
- self.name = only_one(dom2.xpath('/problem/@name'))
- self.rubric=etree.tostring(only_one(dom2.xpath("/rubric/html")))
- self.problem=etree.tostring(only_one(dom2.xpath("/problem")))
- self.lcp = LoncapaProblem(self.problem, self.location.html_id(),
- instance_state, seed=self.seed, system=self.system)
- def get_instance_state(self):
- state = self.lcp.get_state()
- state['attempts'] = self.attempts
- return json.dumps(state)
-
- def get_score(self):
- return self.lcp.get_score()
-
- def max_score(self):
- return self.lcp.get_max_score()
-
- def get_progress(self):
- ''' For now, just return score / max_score
- '''
- d = self.get_score()
- score = d['score']
- total = d['total']
- if total > 0:
- try:
- return Progress(score, total)
- except Exception as err:
- log.exception("Got bad progress")
- return None
- return None
-
- def get_html(self):
- return self.system.render_template('problem_ajax.html', {
- 'element_id': self.location.html_id(),
- 'id': self.id,
- 'ajax_url': self.system.ajax_url,
- })
-
- def get_problem_html(self, encapsulate=True):
- '''Return html for the problem. Adds check, reset, save buttons
- as necessary based on the problem config and state.'''
-
- try:
- html = self.lcp.get_html()
- except Exception, err:
- log.exception(err)
-
- # TODO (vshnayder): another switch on DEBUG.
- if self.system.DEBUG:
- msg = (
- '[courseware.capa.capa_module] '
- 'Failed to generate HTML for problem %s' %
- (self.location.url()))
- msg += '
Error:
%s
' % str(err).replace('<', '<')
- msg += '
%s
' % traceback.format_exc().replace('<', '<')
- html = msg
- else:
- # We're in non-debug mode, and possibly even in production. We want
- # to avoid bricking of problem as much as possible
-
- # Presumably, student submission has corrupted LoncapaProblem HTML.
- # First, pull down all student answers
- student_answers = self.lcp.student_answers
- answer_ids = student_answers.keys()
-
- # Some inputtypes, such as dynamath, have additional "hidden" state that
- # is not exposed to the student. Keep those hidden
- # TODO: Use regex, e.g. 'dynamath' is suffix at end of answer_id
- hidden_state_keywords = ['dynamath']
- for answer_id in answer_ids:
- for hidden_state_keyword in hidden_state_keywords:
- if answer_id.find(hidden_state_keyword) >= 0:
- student_answers.pop(answer_id)
-
- # Next, generate a fresh LoncapaProblem
- self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(),
- state=None, # Tabula rasa
- seed=self.seed, system=self.system)
-
- # Prepend a scary warning to the student
- warning = '
'\
- '
Warning: The problem has been reset to its initial state!
'\
- 'The problem\'s state was corrupted by an invalid submission. '\
- 'The submission consisted of:'\
- '
'
- for student_answer in student_answers.values():
- if student_answer != '':
- warning += '
' + cgi.escape(student_answer) + '
'
- warning += '
'\
- 'If this error persists, please contact the course staff.'\
- '
'
-
- html = warning
- try:
- html += self.lcp.get_html()
- except Exception, err: # Couldn't do it. Give up
- log.exception(err)
- raise
-
- content = {'name': self.display_name,
- 'html': html,
- 'weight': self.descriptor.weight,
- }
-
- # We using strings as truthy values, because the terminology of the
- # check button is context-specific.
-
- # Put a "Check" button if unlimited attempts or still some left
- if self.max_attempts is None or self.attempts < self.max_attempts-1:
- check_button = "Check"
- else:
- # Will be final check so let user know that
- check_button = "Final Check"
-
- reset_button = True
- save_button = True
-
- # If we're after deadline, or user has exhausted attempts,
- # question is read-only.
- if self.closed():
- check_button = False
- reset_button = False
- save_button = False
-
- # User submitted a problem, and hasn't reset. We don't want
- # more submissions.
- if self.lcp.done and self.rerandomize == "always":
- check_button = False
- save_button = False
-
- # Only show the reset button if pressing it will show different values
- if self.rerandomize not in ["always", "onreset"]:
- reset_button = False
-
- # User hasn't submitted an answer yet -- we don't want resets
- if not self.lcp.done:
- reset_button = False
-
- # We may not need a "save" button if infinite number of attempts and
- # non-randomized. The problem author can force it. It's a bit weird for
- # randomization to control this; should perhaps be cleaned up.
- if (self.force_save_button == "false") and (self.max_attempts is None and self.rerandomize != "always"):
- save_button = False
-
- context = {'problem': content,
- 'id': self.id,
- 'check_button': check_button,
- 'reset_button': reset_button,
- 'save_button': save_button,
- 'answer_available': self.answer_available(),
- 'ajax_url': self.system.ajax_url,
- 'attempts_used': self.attempts,
- 'attempts_allowed': self.max_attempts,
- 'progress': self.get_progress(),
- }
-
- html = self.system.render_template('problem.html', context)
- if encapsulate:
- html = '
'.format(
- id=self.location.html_id(), ajax_url=self.system.ajax_url) + html + "
"
-
- # cdodge: OK, we have to do two rounds of url reference subsitutions
- # one which uses the 'asset library' that is served by the contentstore and the
- # more global /static/ filesystem based static content.
- # NOTE: rewrite_content_links is defined in XModule
- # This is a bit unfortunate and I'm sure we'll try to considate this into
- # a one step process.
- html = rewrite_links(html, self.rewrite_content_links)
-
- # now do the substitutions which are filesystem based, e.g. '/static/' prefixes
- return self.system.replace_urls(html, self.metadata['data_dir'])
-
- def handle_ajax(self, dispatch, get):
- '''
- This is called by courseware.module_render, to handle an AJAX call.
- "get" is request.POST.
-
- Returns a json dictionary:
- { 'progress_changed' : True/False,
- 'progress' : 'none'/'in_progress'/'done',
- }
- '''
- handlers = {
- 'problem_get': self.get_problem,
- 'problem_check': self.check_problem,
- 'problem_save': self.save_problem,
- }
-
- if dispatch not in handlers:
- return 'Error'
-
- before = self.get_progress()
- d = handlers[dispatch](get)
- after = self.get_progress()
- d.update({
- 'progress_changed': after != before,
- 'progress_status': Progress.to_js_status_str(after),
- })
- return json.dumps(d, cls=ComplexEncoder)
-
- def closed(self):
- ''' Is the student still allowed to submit answers? '''
- if self.attempts == self.max_attempts:
- return True
- if self.close_date is not None and datetime.datetime.utcnow() > self.close_date:
- return True
-
- return False
-
- def answer_available(self):
- ''' Is the user allowed to see an answer?
- '''
- if self.show_answer == '':
- return False
-
- if self.show_answer == "never":
- return False
-
- # Admins can see the answer, unless the problem explicitly prevents it
- if self.system.user_is_staff:
- return True
-
- if self.show_answer == 'attempted':
- return self.attempts > 0
-
- if self.show_answer == 'answered':
- return self.lcp.done
-
- if self.show_answer == 'closed':
- return self.closed()
-
- if self.show_answer == 'always':
- return True
-
- return False
-
- # Figure out if we should move these to capa_problem?
- def get_problem(self, get):
- ''' Return results of get_problem_html, as a simple dict for json-ing.
- { 'html': }
-
- Used if we want to reconfirm we have the right thing e.g. after
- several AJAX calls.
- '''
- return {'html': self.get_problem_html(encapsulate=False)}
-
- @staticmethod
- def make_dict_of_responses(get):
- '''
- Make dictionary of student responses (aka "answers")
- get is POST dictionary.
- '''
- answers = dict()
- for key in get:
- # e.g. input_resistor_1 ==> resistor_1
- _, _, name = key.partition('_')
-
- # This allows for answers which require more than one value for
- # the same form input (e.g. checkbox inputs). The convention is that
- # if the name ends with '[]' (which looks like an array), then the
- # answer will be an array.
- if not name.endswith('[]'):
- answers[name] = get[key]
- else:
- name = name[:-2]
- answers[name] = get.getlist(key)
-
- return answers
-
- def check_problem(self, get):
- ''' Checks whether answers to a problem are correct, and
- returns a map of correct/incorrect answers:
-
- {'success' : bool,
- 'contents' : html}
- '''
- event_info = dict()
- event_info['state'] = self.lcp.get_state()
- event_info['problem_id'] = self.location.url()
-
- answers = self.make_dict_of_responses(get)
- event_info['answers'] = convert_files_to_filenames(answers)
-
- parsed_answer="incorrect"
- if(answer[answers.keys()[1]]=="Correct"):
- parsed_answer="correct"
-
- # Too late. Cannot submit
- if self.closed():
- event_info['failure'] = 'closed'
- self.system.track_function('save_problem_check_fail', event_info)
- raise NotFoundError('Problem is closed')
-
- try:
- old_state = self.lcp.get_state()
- lcp_id = self.lcp.problem_id
- correct_map = self.lcp.grade_answers(answers)
- correct_map.set(correctness=parsed_answer)
- except StudentInputError as inst:
- # TODO (vshnayder): why is this line here?
- #self.lcp = LoncapaProblem(self.definition['data'],
- # id=lcp_id, state=old_state, system=self.system)
- log.exception("StudentInputError in capa_module:problem_check")
- return {'success': inst.message}
- except Exception, err:
- # TODO: why is this line here?
- #self.lcp = LoncapaProblem(self.definition['data'],
- # id=lcp_id, state=old_state, system=self.system)
- if self.system.DEBUG:
- msg = "Error checking problem: " + str(err)
- msg += '\nTraceback:\n' + traceback.format_exc()
- return {'success': msg}
- log.exception("Error in capa_module problem checking")
- raise Exception("error in capa_module")
-
- self.attempts = self.attempts + 1
- self.lcp.done = True
-
- # success = correct if ALL questions in this problem are correct
- success = 'correct'
- for answer_id in correct_map:
- if not correct_map.is_correct(answer_id):
- success = 'incorrect'
-
- # NOTE: We are logging both full grading and queued-grading submissions. In the latter,
- # 'success' will always be incorrect
- event_info['correct_map'] = correct_map.get_dict()
- event_info['success'] = success
- event_info['attempts'] = self.attempts
- self.system.track_function('save_problem_check', event_info)
-
- # render problem into HTML
- html = self.get_problem_html(encapsulate=False)
-
- return {'success': success,
- 'contents': html,
- }
-
- def save_problem(self, get):
- '''
- Save the passed in answers.
- Returns a dict { 'success' : bool, ['error' : error-msg]},
- with the error key only present if success is False.
- '''
- event_info = dict()
- event_info['state'] = self.lcp.get_state()
- event_info['problem_id'] = self.location.url()
-
- answers = self.make_dict_of_responses(get)
- event_info['answers'] = answers
-
- # Too late. Cannot submit
- if self.closed():
- event_info['failure'] = 'closed'
- self.system.track_function('save_problem_fail', event_info)
- return {'success': False,
- 'error': "Problem is closed"}
-
- # Problem submitted. Student should reset before saving
- # again.
- if self.lcp.done and self.rerandomize == "always":
- event_info['failure'] = 'done'
- self.system.track_function('save_problem_fail', event_info)
- return {'success': False,
- 'error': "Problem needs to be reset prior to save."}
-
- self.lcp.student_answers = answers
-
- # TODO: should this be save_problem_fail? Looks like success to me...
- self.system.track_function('save_problem_fail', event_info)
- return {'success': True}
-
-
-class SelfAssessmentDescriptor(RawDescriptor):
+class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
"""
Module for putting raw html in a course
"""
+ mako_template = "widgets/html-edit.html"
module_class = SelfAssessmentModule
+ filename_extension = "xml"
stores_state = True
has_score = True
- template_dir_name = 'problem'
+ template_dir_name = "html"
- # Capa modules have some additional metadata:
- # TODO (vshnayder): do problems have any other metadata? Do they
- # actually use type and points?
- metadata_attributes = RawDescriptor.metadata_attributes + ('type', 'points')
- # VS[compat]
- # TODO (cpennington): Delete this method once all fall 2012 course are being
- # edited in the cms
+
+ js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]}
+ js_module_name = "HTMLEditingDescriptor"
+
+ # VS[compat] TODO (cpennington): Delete this method once all fall 2012 course
+ # are being edited in the cms
@classmethod
def backcompat_paths(cls, path):
- return [
- 'problem/' + path[8:],
- path[8:],
- ]
+ if path.endswith('.html.xml'):
+ path = path[:-9] + '.html' # backcompat--look for html instead of xml
+ if path.endswith('.html.html'):
+ path = path[:-5] # some people like to include .html in filenames..
+ candidates = []
+ while os.sep in path:
+ candidates.append(path)
+ _, _, path = path.partition(os.sep)
- def __init__(self, *args, **kwargs):
- super(CapaDescriptor, self).__init__(*args, **kwargs)
+ # also look for .html versions instead of .xml
+ nc = []
+ for candidate in candidates:
+ if candidate.endswith('.xml'):
+ nc.append(candidate[:-4] + '.html')
+ return candidates + nc
- weight_string = self.metadata.get('weight', None)
- if weight_string:
- self.weight = float(weight_string)
+ # NOTE: html descriptors are special. We do not want to parse and
+ # export them ourselves, because that can break things (e.g. lxml
+ # adds body tags when it exports, but they should just be html
+ # snippets that will be included in the middle of pages.
+
+ @classmethod
+ def load_definition(cls, xml_object, system, location):
+ '''Load a descriptor from the specified xml_object:
+
+ If there is a filename attribute, load it as a string, and
+ log a warning if it is not parseable by etree.HTMLParser.
+
+ If there is not a filename attribute, the definition is the body
+ of the xml_object, without the root tag (do not want in the
+ middle of a page)
+ '''
+ filename = xml_object.get('filename')
+ if filename is None:
+ definition_xml = copy.deepcopy(xml_object)
+ cls.clean_metadata_from_xml(definition_xml)
+ return {'data': stringify_children(definition_xml)}
else:
- self.weight = None
\ No newline at end of file
+ # html is special. cls.filename_extension is 'xml', but
+ # if 'filename' is in the definition, that means to load
+ # from .html
+ # 'filename' in html pointers is a relative path
+ # (not same as 'html/blah.html' when the pointer is in a directory itself)
+ pointer_path = "{category}/{url_path}".format(category='html',
+ url_path=name_to_pathname(location.name))
+ base = path(pointer_path).dirname()
+ #log.debug("base = {0}, base.dirname={1}, filename={2}".format(base, base.dirname(), filename))
+ filepath = "{base}/{name}.html".format(base=base, name=filename)
+ #log.debug("looking for html file for {0} at {1}".format(location, filepath))
+
+
+
+ # VS[compat]
+ # TODO (cpennington): If the file doesn't exist at the right path,
+ # give the class a chance to fix it up. The file will be written out
+ # again in the correct format. This should go away once the CMS is
+ # online and has imported all current (fall 2012) courses from xml
+ if not system.resources_fs.exists(filepath):
+ candidates = cls.backcompat_paths(filepath)
+ #log.debug("candidates = {0}".format(candidates))
+ for candidate in candidates:
+ if system.resources_fs.exists(candidate):
+ filepath = candidate
+ break
+
+ try:
+ with system.resources_fs.open(filepath) as file:
+ html = file.read()
+ # Log a warning if we can't parse the file, but don't error
+ if not check_html(html):
+ msg = "Couldn't parse html in {0}.".format(filepath)
+ log.warning(msg)
+ system.error_tracker("Warning: " + msg)
+
+ definition = {'data': html}
+
+ # TODO (ichuang): remove this after migration
+ # for Fall 2012 LMS migration: keep filename (and unmangled filename)
+ definition['filename'] = [ filepath, filename ]
+
+ return definition
+
+ except (ResourceNotFoundError) as err:
+ msg = 'Unable to load file contents at path {0}: {1} '.format(
+ filepath, err)
+ # add more info and re-raise
+ raise Exception(msg), None, sys.exc_info()[2]
+
+ # TODO (vshnayder): make export put things in the right places.
+
+ def definition_to_xml(self, resource_fs):
+ '''If the contents are valid xml, write them to filename.xml. Otherwise,
+ write just to filename.xml, and the html
+ string to filename.html.
+ '''
+ try:
+ return etree.fromstring(self.definition['data'])
+ except etree.XMLSyntaxError:
+ pass
+
+ # Not proper format. Write html to file, return an empty tag
+ pathname = name_to_pathname(self.url_name)
+ pathdir = path(pathname).dirname()
+ filepath = u'{category}/{pathname}.html'.format(category=self.category,
+ pathname=pathname)
+
+ resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True)
+ with resource_fs.open(filepath, 'w') as file:
+ file.write(self.definition['data'])
+
+ # write out the relative name
+ relname = path(pathname).basename()
+
+ elt = etree.Element('html')
+ elt.set("filename", relname)
+ return elt
From 74e6834cca71037669da531b4821e178e36d5e9f Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Wed, 31 Oct 2012 18:05:04 -0400
Subject: [PATCH 012/121] move rubric and problem to forms
---
.../xmodule/xmodule/self_assessment_module.py | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index ad42090eea..854c2cf1f3 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -19,7 +19,12 @@ from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent
log = logging.getLogger("mitx.courseware")
+problem_form=('')
+rubric_form=('')
def only_one(lst, default="", process=lambda x: x):
"""
@@ -53,15 +58,17 @@ class SelfAssessmentModule(XModule):
instance_state, shared_state, **kwargs)
dom2=etree.fromstring("" + self.definition['data'] + "")
- rubric=''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))])
- problem=''.join([etree.tostring(child) for child in only_one(dom2.xpath('problem'))])
+ self.rubric=''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))])
+ self.problem=''.join([etree.tostring(child) for child in only_one(dom2.xpath('problem'))])
+
+ self.problem=''.join([self.problem,problem_form])
+
+ self.rubric=''.join([self.rubric,rubric_form])
#print(etree.tostring(rubric))
#print(etree.tostring(problem))
- self.html = etree.tostring(problem)
-
- #self.html=self.definition['data']
+ self.html = self.problem
From 947e6e6e0138b914f5090a9952ab3317c34254e6 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Wed, 31 Oct 2012 18:11:38 -0400
Subject: [PATCH 013/121] working on ajax calls
---
.../js/src/selfassessment/display.coffee | 144 ++----------------
.../xmodule/xmodule/self_assessment_module.py | 28 ++++
2 files changed, 44 insertions(+), 128 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
index 5e16182553..8de2043e3b 100644
--- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
@@ -11,11 +11,9 @@ class @Problem
$(selector, @el)
bind: =>
- problem_prefix = @element_id.replace(/problem_/,'')
+ problem_prefix = @element_id.replace(/sa_/,'')
@inputs = @$("[id^=input_#{problem_prefix}_]")
-
- @$('section.action input:button').click @refreshAnswers
- @$('section.action input.check').click @check
+
@$('section.action input.show').click @show
@$('section.action input.save').click @save
@@ -26,7 +24,7 @@ class @Problem
@setupInputTypes()
@bind()
else
- $.postWithPrefix "#{@url}/problem_get", (response) =>
+ $.postWithPrefix "#{@url}/sa_get", (response) =>
@el.html(response.html)
JavascriptLoader.executeModuleScripts @el, () =>
@setupInputTypes()
@@ -36,86 +34,21 @@ class @Problem
# TODO add hooks for problem types here by inspecting response.html and doing
# stuff if a div w a class is found
- setupInputTypes: =>
- @inputtypeDisplays = {}
- @el.find(".capa_inputtype").each (index, inputtype) =>
- classes = $(inputtype).attr('class').split(' ')
- id = $(inputtype).attr('id')
- for cls in classes
- setupMethod = @inputtypeSetupMethods[cls]
- if setupMethod?
- @inputtypeDisplays[id] = setupMethod(inputtype)
-
- check: =>
- Logger.log 'problem_check', @answers
- $.postWithPrefix "#{@url}/problem_check", @answers, (response) =>
- switch response.success
- when 'incorrect', 'correct'
- @render(response.contents)
- @updateProgress response
- if @el.hasClass 'showed'
- @el.removeClass 'showed'
- else
- @gentle_alert response.success
-
- reset: =>
- Logger.log 'problem_reset', @answers
- $.postWithPrefix "#{@url}/problem_reset", id: @id, (response) =>
- @render(response.html)
- @updateProgress response
-
- # TODO this needs modification to deal with javascript responses; perhaps we
- # need something where responsetypes can define their own behavior when show
- # is called.
show: =>
- if !@el.hasClass 'showed'
- Logger.log 'problem_show', problem: @id
- $.postWithPrefix "#{@url}/problem_show", (response) =>
- answers = response.answers
- $.each answers, (key, value) =>
- if $.isArray(value)
- for choice in value
- @$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true'
- else
- answer = @$("#answer_#{key}, #solution_#{key}")
- answer.html(value)
- Collapsible.setCollapsibles(answer)
+ Logger.log 'sa_show', problem: @id
+ $.postWithPrefix "#{@url}/sa_show", (response) =>
+ answers = response.answers
+ $.each answers, (key, value) =>
+ if $.isArray(value)
+ for choice in value
+ @$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true'
+ else
+ answer = @$("#answer_#{key}, #solution_#{key}")
+ answer.html(value)
+ Collapsible.setCollapsibles(answer)
- # TODO remove the above once everything is extracted into its own
- # inputtype functions.
-
- @el.find(".capa_inputtype").each (index, inputtype) =>
- classes = $(inputtype).attr('class').split(' ')
- for cls in classes
- display = @inputtypeDisplays[$(inputtype).attr('id')]
- showMethod = @inputtypeShowAnswerMethods[cls]
- showMethod(inputtype, display, answers) if showMethod?
-
- @el.find('.problem > div').each (index, element) =>
- MathJax.Hub.Queue ["Typeset", MathJax.Hub, element]
-
- @$('.show').val 'Hide Answer'
- @el.addClass 'showed'
- @updateProgress response
- else
- @$('[id^=answer_], [id^=solution_]').text ''
- @$('[correct_answer]').attr correct_answer: null
- @el.removeClass 'showed'
- @$('.show').val 'Show Answer'
-
- @el.find(".capa_inputtype").each (index, inputtype) =>
- display = @inputtypeDisplays[$(inputtype).attr('id')]
- classes = $(inputtype).attr('class').split(' ')
- for cls in classes
- hideMethod = @inputtypeHideAnswerMethods[cls]
- hideMethod(inputtype, display) if hideMethod?
-
- gentle_alert: (msg) =>
- if @el.find('.capa_alert').length
- @el.find('.capa_alert').remove()
- alert_elem = "
" + msg + "
"
- @el.find('.action').after(alert_elem)
- @el.find('.capa_alert').css(opacity: 0).animate(opacity: 1, 700)
+ @$('.show').val 'Hide Answer'
+ @el.addClass 'showed'
save: =>
Logger.log 'problem_save', @answers
@@ -125,51 +58,6 @@ class @Problem
@gentle_alert saveMessage
@updateProgress response
- refreshAnswers: =>
- @$('input.schematic').each (index, element) ->
- element.schematic.update_value()
- @$(".CodeMirror").each (index, element) ->
- element.CodeMirror.save() if element.CodeMirror.save
- @answers = @inputs.serialize()
-
- inputtypeSetupMethods:
-
- 'text-input-dynamath': (element) =>
- ###
- Return: function (eqn) -> eqn that preprocesses the user formula input before
- it is fed into MathJax. Return 'false' if no preprocessor specified
- ###
- data = $(element).find('.text-input-dynamath_data')
-
- preprocessorClassName = data.data('preprocessor')
- preprocessorClass = window[preprocessorClassName]
- if not preprocessorClass?
- return false
- else
- preprocessor = new preprocessorClass()
- return preprocessor.fn
-
- javascriptinput: (element) =>
-
- data = $(element).find(".javascriptinput_data")
-
- params = data.data("params")
- submission = data.data("submission")
- evaluation = data.data("evaluation")
- problemState = data.data("problem_state")
- displayClass = window[data.data('display_class')]
-
- if evaluation == ''
- evaluation = null
-
- container = $(element).find(".javascriptinput_container")
- submissionField = $(element).find(".javascriptinput_input")
-
- display = new displayClass(problemState, submission, evaluation, container, submissionField, params)
- display.render()
-
- return display
-
inputtypeShowAnswerMethods:
choicegroup: (element, display, answers) =>
element = $(element)
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index 854c2cf1f3..9ed088d406 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -70,6 +70,34 @@ class SelfAssessmentModule(XModule):
self.html = self.problem
+ def handle_ajax(self, dispatch, get):
+ '''
+ This is called by courseware.module_render, to handle an AJAX call.
+ "get" is request.POST.
+
+ Returns a json dictionary:
+ { 'progress_changed' : True/False,
+ 'progress' : 'none'/'in_progress'/'done',
+ }
+ '''
+ handlers = {
+ 'sa_get' : self.show_problem
+ 'sa_show': self.show_rubric,
+ 'sa_save': self.save_problem,
+ }
+
+ if dispatch not in handlers:
+ return 'Error'
+
+ before = self.get_progress()
+ d = handlers[dispatch](get)
+ after = self.get_progress()
+ d.update({
+ 'progress_changed': after != before,
+ 'progress_status': Progress.to_js_status_str(after),
+ })
+ return json.dumps(d, cls=ComplexEncoder)
+
From a91a571fbfbb437f0a0499afec730eff47657357 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Wed, 31 Oct 2012 18:49:58 -0400
Subject: [PATCH 014/121] update javascript
---
common/lib/xmodule/xmodule/capa_module.py | 118 +++++++++---------
.../js/src/selfassessment/display.coffee | 101 +++------------
.../xmodule/xmodule/self_assessment_module.py | 42 ++++---
3 files changed, 106 insertions(+), 155 deletions(-)
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index 76158093b6..46f07796db 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -580,76 +580,76 @@ class CapaModule(XModule):
'contents': html,
}
-def save_problem(self, get):
- '''
- Save the passed in answers.
- Returns a dict { 'success' : bool, ['error' : error-msg]},
- with the error key only present if success is False.
- '''
- event_info = dict()
- event_info['state'] = self.lcp.get_state()
- event_info['problem_id'] = self.location.url()
+ def save_problem(self, get):
+ '''
+ Save the passed in answers.
+ Returns a dict { 'success' : bool, ['error' : error-msg]},
+ with the error key only present if success is False.
+ '''
+ event_info = dict()
+ event_info['state'] = self.lcp.get_state()
+ event_info['problem_id'] = self.location.url()
- answers = self.make_dict_of_responses(get)
- event_info['answers'] = answers
+ answers = self.make_dict_of_responses(get)
+ event_info['answers'] = answers
- # Too late. Cannot submit
- if self.closed():
- event_info['failure'] = 'closed'
+ # Too late. Cannot submit
+ if self.closed():
+ event_info['failure'] = 'closed'
+ self.system.track_function('save_problem_fail', event_info)
+ return {'success': False,
+ 'error': "Problem is closed"}
+
+ # Problem submitted. Student should reset before saving
+ # again.
+ if self.lcp.done and self.rerandomize == "always":
+ event_info['failure'] = 'done'
+ self.system.track_function('save_problem_fail', event_info)
+ return {'success': False,
+ 'error': "Problem needs to be reset prior to save."}
+
+ self.lcp.student_answers = answers
+
+ # TODO: should this be save_problem_fail? Looks like success to me...
self.system.track_function('save_problem_fail', event_info)
- return {'success': False,
- 'error': "Problem is closed"}
+ return {'success': True}
- # Problem submitted. Student should reset before saving
- # again.
- if self.lcp.done and self.rerandomize == "always":
- event_info['failure'] = 'done'
- self.system.track_function('save_problem_fail', event_info)
- return {'success': False,
- 'error': "Problem needs to be reset prior to save."}
+ def reset_problem(self, get):
+ ''' Changes problem state to unfinished -- removes student answers,
+ and causes problem to rerender itself.
- self.lcp.student_answers = answers
+ Returns problem html as { 'html' : html-string }.
+ '''
+ event_info = dict()
+ event_info['old_state'] = self.lcp.get_state()
+ event_info['problem_id'] = self.location.url()
- # TODO: should this be save_problem_fail? Looks like success to me...
- self.system.track_function('save_problem_fail', event_info)
- return {'success': True}
+ if self.closed():
+ event_info['failure'] = 'closed'
+ self.system.track_function('reset_problem_fail', event_info)
+ return {'success': False,
+ 'error': "Problem is closed"}
-def reset_problem(self, get):
- ''' Changes problem state to unfinished -- removes student answers,
- and causes problem to rerender itself.
+ if not self.lcp.done:
+ event_info['failure'] = 'not_done'
+ self.system.track_function('reset_problem_fail', event_info)
+ return {'success': False,
+ 'error': "Refresh the page and make an attempt before resetting."}
- Returns problem html as { 'html' : html-string }.
- '''
- event_info = dict()
- event_info['old_state'] = self.lcp.get_state()
- event_info['problem_id'] = self.location.url()
+ self.lcp.do_reset()
+ if self.rerandomize in ["always", "onreset"]:
+ # reset random number generator seed (note the self.lcp.get_state()
+ # in next line)
+ self.lcp.seed = None
- if self.closed():
- event_info['failure'] = 'closed'
- self.system.track_function('reset_problem_fail', event_info)
- return {'success': False,
- 'error': "Problem is closed"}
+ self.lcp = LoncapaProblem(self.definition['data'],
+ self.location.html_id(), self.lcp.get_state(),
+ system=self.system)
- if not self.lcp.done:
- event_info['failure'] = 'not_done'
- self.system.track_function('reset_problem_fail', event_info)
- return {'success': False,
- 'error': "Refresh the page and make an attempt before resetting."}
+ event_info['new_state'] = self.lcp.get_state()
+ self.system.track_function('reset_problem', event_info)
- self.lcp.do_reset()
- if self.rerandomize in ["always", "onreset"]:
- # reset random number generator seed (note the self.lcp.get_state()
- # in next line)
- self.lcp.seed = None
-
- self.lcp = LoncapaProblem(self.definition['data'],
- self.location.html_id(), self.lcp.get_state(),
- system=self.system)
-
- event_info['new_state'] = self.lcp.get_state()
- self.system.track_function('reset_problem', event_info)
-
- return {'html': self.get_problem_html(encapsulate=False)}
+ return {'html': self.get_problem_html(encapsulate=False)}
class CapaDescriptor(RawDescriptor):
diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
index 8de2043e3b..aabe0cc0e5 100644
--- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
@@ -1,84 +1,21 @@
-class @Problem
+show: =>
+ Logger.log 'sa_show', problem: @id
+ $.postWithPrefix "#{@url}/sa_show", (response) =>
+ answers = response.answers
+ $.each answers, (key, value) =>
+ if $.isArray(value)
+ for choice in value
+ @$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true'
+ else
+ answer = @$("#answer_#{key}, #solution_#{key}")
+ answer.html(value)
+ Collapsible.setCollapsibles(answer)
- constructor: (element) ->
- @el = $(element).find('.problems-wrapper')
- @id = @el.data('problem-id')
- @element_id = @el.attr('id')
- @url = @el.data('url')
- @render()
+ @$('.show').val 'Hide Answer'
+ @el.addClass 'showed'
- $: (selector) ->
- $(selector, @el)
-
- bind: =>
- problem_prefix = @element_id.replace(/sa_/,'')
- @inputs = @$("[id^=input_#{problem_prefix}_]")
-
- @$('section.action input.show').click @show
- @$('section.action input.save').click @save
-
- render: (content) ->
- if content
- @el.html(content)
- JavascriptLoader.executeModuleScripts @el, () =>
- @setupInputTypes()
- @bind()
- else
- $.postWithPrefix "#{@url}/sa_get", (response) =>
- @el.html(response.html)
- JavascriptLoader.executeModuleScripts @el, () =>
- @setupInputTypes()
- @bind()
-
-
- # TODO add hooks for problem types here by inspecting response.html and doing
- # stuff if a div w a class is found
-
- show: =>
- Logger.log 'sa_show', problem: @id
- $.postWithPrefix "#{@url}/sa_show", (response) =>
- answers = response.answers
- $.each answers, (key, value) =>
- if $.isArray(value)
- for choice in value
- @$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true'
- else
- answer = @$("#answer_#{key}, #solution_#{key}")
- answer.html(value)
- Collapsible.setCollapsibles(answer)
-
- @$('.show').val 'Hide Answer'
- @el.addClass 'showed'
-
- save: =>
- Logger.log 'problem_save', @answers
- $.postWithPrefix "#{@url}/problem_save", @answers, (response) =>
- if response.success
- saveMessage = "Your answers have been saved but not graded. Hit 'Check' to grade them."
- @gentle_alert saveMessage
- @updateProgress response
-
- inputtypeShowAnswerMethods:
- choicegroup: (element, display, answers) =>
- element = $(element)
-
- element.find('input').attr('disabled', 'disabled')
-
- input_id = element.attr('id').replace(/inputtype_/,'')
- answer = answers[input_id]
- for choice in answer
- element.find("label[for='input_#{input_id}_#{choice}']").addClass 'choicegroup_correct'
-
- javascriptinput: (element, display, answers) =>
- answer_id = $(element).attr('id').split("_")[1...].join("_")
- answer = JSON.parse(answers[answer_id])
- display.showAnswer(answer)
-
- inputtypeHideAnswerMethods:
- choicegroup: (element, display) =>
- element = $(element)
- element.find('input').attr('disabled', null)
- element.find('label').removeClass('choicegroup_correct')
-
- javascriptinput: (element, display) =>
- display.hideAnswer()
+save: =>
+ Logger.log 'sa_save', @answers
+ $.postWithPrefix "#{@url}/sa_save", @answers, (response) =>
+ if response.success
+ @$('p.rubric').replace(response.rubric)
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index 9ed088d406..41ac6a4f7b 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -19,12 +19,12 @@ from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent
log = logging.getLogger("mitx.courseware")
-problem_form=('')
+problem_form=('')
-rubric_form=('')
+ 'Incorrect')
def only_one(lst, default="", process=lambda x: x):
"""
@@ -46,7 +46,7 @@ class SelfAssessmentModule(XModule):
resource_string(__name__, 'js/src/selfassessment/display.coffee')
]
}
- js_module_name = "SelfAssessmentModule"
+ js_module_name = "SelfAssessment"
def get_html(self):
# cdodge: perform link substitutions for any references to course static content (e.g. images)
@@ -71,17 +71,18 @@ class SelfAssessmentModule(XModule):
self.html = self.problem
def handle_ajax(self, dispatch, get):
- '''
- This is called by courseware.module_render, to handle an AJAX call.
- "get" is request.POST.
+ '''
+ This is called by courseware.module_render, to handle an AJAX call.
+ "get" is request.POST.
+
+ Returns a json dictionary:
+ { 'progress_changed' : True/False,
+ 'progress' : 'none'/'in_progress'/'done',
+ }
+ '''
- Returns a json dictionary:
- { 'progress_changed' : True/False,
- 'progress' : 'none'/'in_progress'/'done',
- }
- '''
handlers = {
- 'sa_get' : self.show_problem
+ 'sa_get' : self.show_problem,
'sa_show': self.show_rubric,
'sa_save': self.save_problem,
}
@@ -98,6 +99,19 @@ class SelfAssessmentModule(XModule):
})
return json.dumps(d, cls=ComplexEncoder)
+ def save_problem(self, get):
+ '''
+ Save the passed in answers.
+ Returns a dict { 'success' : bool, ['error' : error-msg]},
+ with the error key only present if success is False.
+ '''
+ event_info = dict()
+ event_info['state'] = self.lcp.get_state()
+ event_info['problem_id'] = self.location.url()
+
+ return {'success': True, 'rubric' : self.rubric}
+
+
From 75adeed063866dd66fd9eb3bc1bea20ce21a2e0b Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Wed, 31 Oct 2012 20:04:21 -0400
Subject: [PATCH 015/121] changing coffee file
---
.../js/src/selfassessment/display.coffee | 163 ++++++++++++++++--
.../xmodule/js/src/selfassessment/display.js | 20 +++
.../xmodule/xmodule/self_assessment_module.py | 8 +-
3 files changed, 169 insertions(+), 22 deletions(-)
create mode 100644 common/lib/xmodule/xmodule/js/src/selfassessment/display.js
diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
index aabe0cc0e5..91582642b8 100644
--- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
@@ -1,21 +1,148 @@
-show: =>
- Logger.log 'sa_show', problem: @id
- $.postWithPrefix "#{@url}/sa_show", (response) =>
- answers = response.answers
- $.each answers, (key, value) =>
- if $.isArray(value)
- for choice in value
- @$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true'
+class @SelfAssessment
+
+ constructor: (element) ->
+ @el = $(element).find('.sa-wrapper')
+ @id = @el.data('sa-id')
+ @element_id = @el.attr('id')
+ @url = @el.data('url')
+ @render()
+
+ $: (selector) ->
+ $(selector, @el)
+
+ bind: =>
+
+ window.update_schematics()
+
+ problem_prefix = @element_id.replace(/sa_/,'')
+ @inputs = @$("[id^=input_#{problem_prefix}_]")
+
+ @$('input:button').click @refreshAnswers
+ #@$('section.action input.check').click @check
+ @$('input.show').click @show
+ @$('input.save').click @save
+
+ # Collapsibles
+ Collapsible.setCollapsibles(@el)
+
+ # Dynamath
+ @$('input.math').keyup(@refreshMath)
+ @$('input.math').each (index, element) =>
+ MathJax.Hub.Queue [@refreshMath, null, element]
+
+ updateProgress: (response) =>
+ if response.progress_changed
+ @el.attr progress: response.progress_status
+ @el.trigger('progressChanged')
+
+ show: =>
+ $.postWithPrefix "#{@url}/sa_show", (response) =>
+ answers = response.answers
+ @el.addClass 'showed'
+
+ save: =>
+ $.postWithPrefix "/sa_save", @answers, (response) =>
+ if response.success
+ @$('p.rubric').replace(response.rubric)
+
+ render: (content) ->
+ if content
+ @el.html(content)
+ JavascriptLoader.executeModuleScripts @el, () =>
+ @setupInputTypes()
+ @bind()
+ else
+ $.postWithPrefix "#{@url}/problem_get", (response) =>
+ @el.html(response.html)
+ JavascriptLoader.executeModuleScripts @el, () =>
+ @setupInputTypes()
+ @bind()
+
+ setupInputTypes: =>
+ @inputtypeDisplays = {}
+ @el.find(".capa_inputtype").each (index, inputtype) =>
+ classes = $(inputtype).attr('class').split(' ')
+ id = $(inputtype).attr('id')
+ for cls in classes
+ setupMethod = @inputtypeSetupMethods[cls]
+ if setupMethod?
+ @inputtypeDisplays[id] = setupMethod(inputtype)
+
+ gentle_alert: (msg) =>
+ if @el.find('.capa_alert').length
+ @el.find('.capa_alert').remove()
+ alert_elem = "
').format(self.location)
self.problem=''.join([self.problem,problem_form])
self.rubric=''.join([self.rubric,rubric_form])
self.html = self.problem
@@ -151,6 +151,7 @@ class SelfAssessmentModule(XModule):
event_info['problem_id'] = self.location.url()
answers = self.make_dict_of_responses(get)
+ log.debug(answers)
event_info['answers'] = answers
self.system.track_function('save_problem_succeed', event_info)
From 5b6045b5fceb14d39d2b1fef6a917bde3a6b0b75 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Thu, 1 Nov 2012 11:47:28 -0400
Subject: [PATCH 022/121] fixed ajax value passing to function
---
.../js/src/selfassessment/display.coffee | 13 +------
.../xmodule/xmodule/self_assessment_module.py | 38 ++++---------------
2 files changed, 8 insertions(+), 43 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
index f393fc79ec..5627325433 100644
--- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
@@ -9,23 +9,12 @@ $(document).on('click', 'section.sa-wrapper input#show', ( ->
));
$(document).on('click', 'section.sa-wrapper input#save', ( ->
- answer=$('section.sa-wrapper input#answer').val()
- assessment=0
assessment_correct=$('section.sa-wrapper #assessment').find(':selected').text()
- alert(assessment_correct)
-
root = location.protocol + "//" + location.host
post_url=$('section.sa-wrapper input#show').attr('url')
final_url="/courses/MITx/6.002x/2012_Fall/modx/#{post_url}/sa_save"
- $('section.sa-wrapper input#assessment option').each( ->
- if (this.selected)
- alert('this option is selected')
- else
- alert('this is not')
- );
-
- $.post final_url, answer, (response) ->
+ $.post final_url, assessment_correct, (response) ->
if response.success
$('section.sa_wrapper p#save_message').replace(response.message)
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index 3e65b16670..22c5b9c9f3 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -72,7 +72,7 @@ class SelfAssessmentModule(XModule):
self.problem=''.join([self.problem,problem_form])
self.rubric=''.join([self.rubric,rubric_form])
self.html = self.problem
- self.answers={}
+ self.answer=""
self.score=0
self.top_score=1
@@ -124,22 +124,23 @@ class SelfAssessmentModule(XModule):
})
return json.dumps(d, cls=ComplexEncoder)
-
-
def show_rubric(self,get):
+ self.answer=get.keys()[0]
return {'success': True, 'rubric' : self.rubric}
-
def save_problem(self, get):
'''
Save the passed in answers.
Returns a dict { 'success' : bool, ['error' : error-msg]},
with the error key only present if success is False.
'''
+
+ correctness=get.keys()[0]
+ log.debug(correctness)
event_info = dict()
event_info['state'] = {'seed': 1,
'student_answers': self.answers,
- 'correct_map': {'self_assess' : {'correctness': False,
+ 'correct_map': {'self_assess' : {'correctness': correctness,
'npoints': 0,
'msg': "",
'hint': "",
@@ -150,37 +151,12 @@ class SelfAssessmentModule(XModule):
event_info['problem_id'] = self.location.url()
- answers = self.make_dict_of_responses(get)
- log.debug(answers)
- event_info['answers'] = answers
+ event_info['answers'] = self.answer
self.system.track_function('save_problem_succeed', event_info)
return {'success': True, 'message' : "Save Succcesful. Thanks for participating!"}
- @staticmethod
- def make_dict_of_responses(get):
- '''Make dictionary of student responses (aka "answers")
- get is POST dictionary.
- '''
- answers = dict()
- for key in get:
- # e.g. input_resistor_1 ==> resistor_1
- _, _, name = key.partition('_')
-
- # This allows for answers which require more than one value for
- # the same form input (e.g. checkbox inputs). The convention is that
- # if the name ends with '[]' (which looks like an array), then the
- # answer will be an array.
- if not name.endswith('[]'):
- answers[name] = get[key]
- else:
- name = name[:-2]
- answers[name] = get.getlist(key)
-
- return answers
-
-
class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
"""
Module for putting raw html in a course
From 9238653f7abf84eb001dce15b55a2a70599b2fef Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Thu, 1 Nov 2012 12:27:42 -0400
Subject: [PATCH 023/121] fixed js to remove buttons when needed
---
.../js/src/selfassessment/display.coffee | 15 ++++----
.../xmodule/xmodule/self_assessment_module.py | 35 +++++++++++++------
2 files changed, 32 insertions(+), 18 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
index 5627325433..add21ee927 100644
--- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
@@ -2,21 +2,22 @@ $(document).on('click', 'section.sa-wrapper input#show', ( ->
root = location.protocol + "//" + location.host
post_url=$('section.sa-wrapper input#show').attr('url')
final_url="/courses/MITx/6.002x/2012_Fall/modx/#{post_url}/sa_show"
- answer=$('section.sa-wrapper input#answer').val()
+ answer=$('section.sa-wrapper textarea#answer').val()
$.post final_url, answer, (response) ->
if response.success
+ $('section.sa-wrapper input#show').remove()
+ $('section.sa-wrapper textarea#answer').remove()
+ $('section.sa-wrapper p#rubric').append(answer)
$('section.sa-wrapper p#rubric').append(response.rubric)
));
$(document).on('click', 'section.sa-wrapper input#save', ( ->
assessment_correct=$('section.sa-wrapper #assessment').find(':selected').text()
root = location.protocol + "//" + location.host
- post_url=$('section.sa-wrapper input#show').attr('url')
+ post_url=$('section.sa-wrapper input#save').attr('url')
final_url="/courses/MITx/6.002x/2012_Fall/modx/#{post_url}/sa_save"
-
$.post final_url, assessment_correct, (response) ->
- if response.success
- $('section.sa_wrapper p#save_message').replace(response.message)
-
- alert("save")
+ if response.success
+ $('section.sa-wrapper p#save_message').append(response.message)
+ $('section.sa-wrapper input#save').remove()
));
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index 22c5b9c9f3..5c4cda71f7 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -21,10 +21,6 @@ from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent
log = logging.getLogger("mitx.courseware")
-rubric_form=('
Please assess your performance given the above rubric:
'
- '
')
-
def only_one(lst, default="", process=lambda x: x):
"""
If lst is empty, returns default
@@ -63,18 +59,31 @@ class SelfAssessmentModule(XModule):
instance_state, shared_state, **kwargs)
dom2=etree.fromstring("" + self.definition['data'] + "")
- self.rubric=''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))])
+ self.rubric="
" + ''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))])
self.problem=''.join([etree.tostring(child) for child in only_one(dom2.xpath('problem'))])
- problem_form=(' '
+ problem_form=(' '
''
'
').format(self.location)
+
+ rubric_form=('
Please assess your performance given the above rubric: '
+ ' '
+ ''
+ '
').format(self.location)
+
+
+
self.problem=''.join([self.problem,problem_form])
self.rubric=''.join([self.rubric,rubric_form])
self.html = self.problem
self.answer=""
self.score=0
self.top_score=1
+ self.submit_message=etree.tostring(dom2.xpath('submitmessage')[0])
+ log.debug(self.submit_message)
+
def get_score(self):
return self.score
@@ -126,6 +135,7 @@ class SelfAssessmentModule(XModule):
def show_rubric(self,get):
self.answer=get.keys()[0]
+ log.debug(self.answer)
return {'success': True, 'rubric' : self.rubric}
def save_problem(self, get):
@@ -135,13 +145,16 @@ class SelfAssessmentModule(XModule):
with the error key only present if success is False.
'''
- correctness=get.keys()[0]
+ correctness=get.keys()[0].lower()
log.debug(correctness)
+ points=0
+ if correctness=="correct" :
+ points=1
event_info = dict()
event_info['state'] = {'seed': 1,
- 'student_answers': self.answers,
+ 'student_answers': self.answer,
'correct_map': {'self_assess' : {'correctness': correctness,
- 'npoints': 0,
+ 'npoints': points,
'msg': "",
'hint': "",
'hintmode': "",
@@ -155,7 +168,7 @@ class SelfAssessmentModule(XModule):
self.system.track_function('save_problem_succeed', event_info)
- return {'success': True, 'message' : "Save Succcesful. Thanks for participating!"}
+ return {'success': True, 'message' : self.submit_message}
class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
"""
From 7b81f250164ce0b62a368d729d53dbd657153d82 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Thu, 1 Nov 2012 13:09:11 -0400
Subject: [PATCH 024/121] add state functions
---
.../xmodule/xmodule/self_assessment_module.py | 41 +++++++++++++++++--
1 file changed, 37 insertions(+), 4 deletions(-)
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index 5c4cda71f7..cd443a9de2 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -84,6 +84,21 @@ class SelfAssessmentModule(XModule):
self.submit_message=etree.tostring(dom2.xpath('submitmessage')[0])
log.debug(self.submit_message)
+ self.attempts = 0
+ self.max_attempts = 1
+ self.max_attempts = self.metadata.get('attempts', None)
+ if self.max_attempts is not None:
+ self.max_attempts = int(self.max_attempts)
+ else:
+ self.max_attempts=1
+
+ if instance_state is not None:
+ instance_state = json.loads(instance_state)
+ if instance_state is not None and 'attempts' in instance_state:
+ self.attempts = instance_state['attempts']
+
+ self.correctness="incorrect"
+
def get_score(self):
return self.score
@@ -145,15 +160,15 @@ class SelfAssessmentModule(XModule):
with the error key only present if success is False.
'''
- correctness=get.keys()[0].lower()
- log.debug(correctness)
+ self.correctness=get.keys()[0].lower()
+ log.debug(self.correctness)
points=0
- if correctness=="correct" :
+ if self.correctness=="correct" :
points=1
event_info = dict()
event_info['state'] = {'seed': 1,
'student_answers': self.answer,
- 'correct_map': {'self_assess' : {'correctness': correctness,
+ 'correct_map': {'self_assess' : {'correctness': self.correctness,
'npoints': points,
'msg': "",
'hint': "",
@@ -170,6 +185,24 @@ class SelfAssessmentModule(XModule):
return {'success': True, 'message' : self.submit_message}
+ def get_instance_state(self):
+ points=0
+ if self.correctness=="correct" :
+ points=1
+ state= return {'seed': 1,
+ 'student_answers': self.answer,
+ 'correct_map': {'self_assess' : {'correctness': self.correctness,
+ 'npoints': points,
+ 'msg': "",
+ 'hint': "",
+ 'hintmode': "",
+ 'queuestate': "",
+ }},
+ 'done': self.done}
+ state['attempts'] = self.attempts
+ return json.dumps(state)
+
+
class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
"""
Module for putting raw html in a course
From a28b4ce18e4c9d87fd8af05eb2fe8fada5f6e4ee Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Thu, 1 Nov 2012 13:32:05 -0400
Subject: [PATCH 025/121] added extensions to persist state
---
.../js/src/selfassessment/display.coffee | 2 --
.../xmodule/xmodule/self_assessment_module.py | 23 +++++++++++++++----
2 files changed, 19 insertions(+), 6 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
index add21ee927..b6aaf90bdb 100644
--- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
@@ -1,5 +1,4 @@
$(document).on('click', 'section.sa-wrapper input#show', ( ->
- root = location.protocol + "//" + location.host
post_url=$('section.sa-wrapper input#show').attr('url')
final_url="/courses/MITx/6.002x/2012_Fall/modx/#{post_url}/sa_show"
answer=$('section.sa-wrapper textarea#answer').val()
@@ -13,7 +12,6 @@ $(document).on('click', 'section.sa-wrapper input#show', ( ->
$(document).on('click', 'section.sa-wrapper input#save', ( ->
assessment_correct=$('section.sa-wrapper #assessment').find(':selected').text()
- root = location.protocol + "//" + location.host
post_url=$('section.sa-wrapper input#save').attr('url')
final_url="/courses/MITx/6.002x/2012_Fall/modx/#{post_url}/sa_save"
$.post final_url, assessment_correct, (response) ->
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index cd443a9de2..438f2601e6 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -82,22 +82,34 @@ class SelfAssessmentModule(XModule):
self.score=0
self.top_score=1
self.submit_message=etree.tostring(dom2.xpath('submitmessage')[0])
- log.debug(self.submit_message)
self.attempts = 0
- self.max_attempts = 1
self.max_attempts = self.metadata.get('attempts', None)
if self.max_attempts is not None:
self.max_attempts = int(self.max_attempts)
else:
self.max_attempts=1
+ self.correctness="incorrect"
+ self.done=False
if instance_state is not None:
instance_state = json.loads(instance_state)
+ log.debug(instance_state)
+
if instance_state is not None and 'attempts' in instance_state:
self.attempts = instance_state['attempts']
- self.correctness="incorrect"
+ if instance_state is not None and 'student_answers' in instance_state:
+ self.answer=instance_state['student_answers']
+
+ if instance_state is not None and 'done' in instance_state:
+ self.done=instance_state['done']
+
+ if instance_state is not None and 'correct_map' in instance_state:
+ if 'self_assess' in instance_state['correct_map']:
+ self.score=instance_state['correct_map']['self_assess']['npoints']
+ self.correctness=instance_state['correct_map']['self_assess']['correctness']
+
def get_score(self):
@@ -166,6 +178,7 @@ class SelfAssessmentModule(XModule):
if self.correctness=="correct" :
points=1
event_info = dict()
+ self.done=True
event_info['state'] = {'seed': 1,
'student_answers': self.answer,
'correct_map': {'self_assess' : {'correctness': self.correctness,
@@ -181,6 +194,8 @@ class SelfAssessmentModule(XModule):
event_info['answers'] = self.answer
+ self.attempts = self.attempts + 1
+
self.system.track_function('save_problem_succeed', event_info)
return {'success': True, 'message' : self.submit_message}
@@ -189,7 +204,7 @@ class SelfAssessmentModule(XModule):
points=0
if self.correctness=="correct" :
points=1
- state= return {'seed': 1,
+ state= {'seed': 1,
'student_answers': self.answer,
'correct_map': {'self_assess' : {'correctness': self.correctness,
'npoints': points,
From a9a73e9a76b31aeb7150376655a9d8d27967c2b4 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Thu, 1 Nov 2012 13:42:34 -0400
Subject: [PATCH 026/121] capa module integration
---
.../js/src/selfassessment/display.coffee | 2 ++
.../xmodule/xmodule/self_assessment_module.py | 20 +++++++++++--------
2 files changed, 14 insertions(+), 8 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
index b6aaf90bdb..7bdab972e0 100644
--- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
@@ -8,6 +8,8 @@ $(document).on('click', 'section.sa-wrapper input#show', ( ->
$('section.sa-wrapper textarea#answer').remove()
$('section.sa-wrapper p#rubric').append(answer)
$('section.sa-wrapper p#rubric').append(response.rubric)
+ else
+ $('section.sa-wrapper p#rubric').append(response.message)
));
$(document).on('click', 'section.sa-wrapper input#save', ( ->
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index 438f2601e6..950aec3322 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -21,6 +21,8 @@ from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent
log = logging.getLogger("mitx.courseware")
+max_attempts=100
+
def only_one(lst, default="", process=lambda x: x):
"""
If lst is empty, returns default
@@ -88,7 +90,7 @@ class SelfAssessmentModule(XModule):
if self.max_attempts is not None:
self.max_attempts = int(self.max_attempts)
else:
- self.max_attempts=1
+ self.max_attempts=max_attempts
self.correctness="incorrect"
self.done=False
@@ -111,9 +113,8 @@ class SelfAssessmentModule(XModule):
self.correctness=instance_state['correct_map']['self_assess']['correctness']
-
def get_score(self):
- return self.score
+ return {'score' : self.score}
def max_score(self):
return self.top_score
@@ -121,8 +122,8 @@ class SelfAssessmentModule(XModule):
def get_progress(self):
''' For now, just return score / max_score
'''
- score = self.get_score()
- total = self.max_score()
+ score = self.score
+ total = self.top_score
if total > 0:
try:
return Progress(score, total)
@@ -161,9 +162,12 @@ class SelfAssessmentModule(XModule):
return json.dumps(d, cls=ComplexEncoder)
def show_rubric(self,get):
- self.answer=get.keys()[0]
- log.debug(self.answer)
- return {'success': True, 'rubric' : self.rubric}
+ if(self.attempts
Date: Thu, 1 Nov 2012 14:07:50 -0400
Subject: [PATCH 027/121] fix ajax url issue
---
.../xmodule/xmodule/js/src/selfassessment/display.coffee | 4 ++--
common/lib/xmodule/xmodule/self_assessment_module.py | 6 ++----
2 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
index 7bdab972e0..b1a10ef411 100644
--- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
@@ -1,6 +1,6 @@
$(document).on('click', 'section.sa-wrapper input#show', ( ->
post_url=$('section.sa-wrapper input#show').attr('url')
- final_url="/courses/MITx/6.002x/2012_Fall/modx/#{post_url}/sa_show"
+ final_url="#{post_url}/sa_show"
answer=$('section.sa-wrapper textarea#answer').val()
$.post final_url, answer, (response) ->
if response.success
@@ -15,7 +15,7 @@ $(document).on('click', 'section.sa-wrapper input#show', ( ->
$(document).on('click', 'section.sa-wrapper input#save', ( ->
assessment_correct=$('section.sa-wrapper #assessment').find(':selected').text()
post_url=$('section.sa-wrapper input#save').attr('url')
- final_url="/courses/MITx/6.002x/2012_Fall/modx/#{post_url}/sa_save"
+ final_url="#{post_url}/sa_save"
$.post final_url, assessment_correct, (response) ->
if response.success
$('section.sa-wrapper p#save_message').append(response.message)
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index 950aec3322..d66cc2fbbc 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -66,16 +66,14 @@ class SelfAssessmentModule(XModule):
problem_form=(' '
''
- '
').format(self.location)
+ '
').format(system.ajax_url)
rubric_form=('
Please assess your performance given the above rubric: '
' '
''
- '
').format(self.location)
-
-
+ '
').format(system.ajax_url)
self.problem=''.join([self.problem,problem_form])
self.rubric=''.join([self.rubric,rubric_form])
From db7bdd84c89631c3e8974545245aea9ccc6f697c Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Thu, 1 Nov 2012 14:23:13 -0400
Subject: [PATCH 028/121] add documentation and clean up code
---
.../xmodule/xmodule/self_assessment_module.py | 68 ++++++++++++++++---
1 file changed, 58 insertions(+), 10 deletions(-)
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index d66cc2fbbc..d2175a4ed7 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -1,3 +1,9 @@
+"""
+Add Self Assessment module so students can write essay, submit, then see a rubric and rate themselves.
+Incredibly hacky solution to persist state and properly display information
+"""
+
+
import copy
from fs.errors import ResourceNotFoundError
import logging
@@ -21,6 +27,7 @@ from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent
log = logging.getLogger("mitx.courseware")
+#Set the default number of max attempts. Should be 1 for production
max_attempts=100
def only_one(lst, default="", process=lambda x: x):
@@ -60,9 +67,35 @@ class SelfAssessmentModule(XModule):
XModule.__init__(self, system, location, definition, descriptor,
instance_state, shared_state, **kwargs)
+ """
+ Definition file should have 3 blocks -- problem, rubric, and submitmessage
+ Sample file:
+
+
+
+ Insert problem text here.
+
+
+ Insert grading rubric here.
+
+
+ Thanks for submitting!
+
+
+
+
+ """
+
+ #Parse definition file
dom2=etree.fromstring("" + self.definition['data'] + "")
+
+ #Extract problem, submission message and rubric from definition file
self.rubric="
" + ''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))])
self.problem=''.join([etree.tostring(child) for child in only_one(dom2.xpath('problem'))])
+ self.submit_message=etree.tostring(dom2.xpath('submitmessage')[0])
+
+ #Forms to append to problem and rubric that capture student responses.
+ #Do not change ids and names, as javascript (selfassessment/display.coffee) depends on them
problem_form=(' '
''
@@ -75,23 +108,28 @@ class SelfAssessmentModule(XModule):
''
'
').format(system.ajax_url)
+ #Combine problem, rubric, and the forms
self.problem=''.join([self.problem,problem_form])
self.rubric=''.join([self.rubric,rubric_form])
+
+ #Display the problem to the student to begin with
self.html = self.problem
+
+ #Initialize variables
self.answer=""
self.score=0
self.top_score=1
- self.submit_message=etree.tostring(dom2.xpath('submitmessage')[0])
-
self.attempts = 0
+ self.correctness="incorrect"
+ self.done=False
self.max_attempts = self.metadata.get('attempts', None)
+
+ #Pull variables from instance state if available
if self.max_attempts is not None:
self.max_attempts = int(self.max_attempts)
else:
self.max_attempts=max_attempts
- self.correctness="incorrect"
- self.done=False
if instance_state is not None:
instance_state = json.loads(instance_state)
log.debug(instance_state)
@@ -160,6 +198,10 @@ class SelfAssessmentModule(XModule):
return json.dumps(d, cls=ComplexEncoder)
def show_rubric(self,get):
+ """
+ After the problem is submitted, show the rubric
+ """
+ #Check to see if attempts are less than max
if(self.attempts
Date: Thu, 1 Nov 2012 14:24:06 -0400
Subject: [PATCH 029/121] reformat code with spaces
---
.../xmodule/xmodule/self_assessment_module.py | 130 +++++++++---------
1 file changed, 65 insertions(+), 65 deletions(-)
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index d2175a4ed7..003d3bf36f 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -3,7 +3,6 @@ Add Self Assessment module so students can write essay, submit, then see a rubri
Incredibly hacky solution to persist state and properly display information
"""
-
import copy
from fs.errors import ResourceNotFoundError
import logging
@@ -28,7 +27,7 @@ from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent
log = logging.getLogger("mitx.courseware")
#Set the default number of max attempts. Should be 1 for production
-max_attempts=100
+max_attempts = 100
def only_one(lst, default="", process=lambda x: x):
"""
@@ -43,6 +42,7 @@ def only_one(lst, default="", process=lambda x: x):
else:
raise Exception('Malformed XML')
+
class ComplexEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, complex):
@@ -87,48 +87,48 @@ class SelfAssessmentModule(XModule):
"""
#Parse definition file
- dom2=etree.fromstring("" + self.definition['data'] + "")
+ dom2 = etree.fromstring("" + self.definition['data'] + "")
#Extract problem, submission message and rubric from definition file
- self.rubric="
" + ''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))])
- self.problem=''.join([etree.tostring(child) for child in only_one(dom2.xpath('problem'))])
- self.submit_message=etree.tostring(dom2.xpath('submitmessage')[0])
+ self.rubric = "
" + ''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))])
+ self.problem = ''.join([etree.tostring(child) for child in only_one(dom2.xpath('problem'))])
+ self.submit_message = etree.tostring(dom2.xpath('submitmessage')[0])
#Forms to append to problem and rubric that capture student responses.
#Do not change ids and names, as javascript (selfassessment/display.coffee) depends on them
- problem_form=(' '
- ''
- '
Please assess your performance given the above rubric: '
- ' '
- ''
- '
').format(system.ajax_url)
+ rubric_form = ('
Please assess your performance given the above rubric: '
+ ' '
+ ''
+ '
').format(system.ajax_url)
#Combine problem, rubric, and the forms
- self.problem=''.join([self.problem,problem_form])
- self.rubric=''.join([self.rubric,rubric_form])
+ self.problem = ''.join([self.problem, problem_form])
+ self.rubric = ''.join([self.rubric, rubric_form])
#Display the problem to the student to begin with
self.html = self.problem
#Initialize variables
- self.answer=""
- self.score=0
- self.top_score=1
+ self.answer = ""
+ self.score = 0
+ self.top_score = 1
self.attempts = 0
- self.correctness="incorrect"
- self.done=False
+ self.correctness = "incorrect"
+ self.done = False
self.max_attempts = self.metadata.get('attempts', None)
#Pull variables from instance state if available
if self.max_attempts is not None:
self.max_attempts = int(self.max_attempts)
else:
- self.max_attempts=max_attempts
+ self.max_attempts = max_attempts
if instance_state is not None:
instance_state = json.loads(instance_state)
@@ -138,19 +138,19 @@ class SelfAssessmentModule(XModule):
self.attempts = instance_state['attempts']
if instance_state is not None and 'student_answers' in instance_state:
- self.answer=instance_state['student_answers']
+ self.answer = instance_state['student_answers']
if instance_state is not None and 'done' in instance_state:
- self.done=instance_state['done']
+ self.done = instance_state['done']
if instance_state is not None and 'correct_map' in instance_state:
if 'self_assess' in instance_state['correct_map']:
- self.score=instance_state['correct_map']['self_assess']['npoints']
- self.correctness=instance_state['correct_map']['self_assess']['correctness']
+ self.score = instance_state['correct_map']['self_assess']['npoints']
+ self.correctness = instance_state['correct_map']['self_assess']['correctness']
def get_score(self):
- return {'score' : self.score}
+ return {'score': self.score}
def max_score(self):
return self.top_score
@@ -183,7 +183,7 @@ class SelfAssessmentModule(XModule):
handlers = {
'sa_show': self.show_rubric,
'sa_save': self.save_problem,
- }
+ }
if dispatch not in handlers:
return 'Error'
@@ -194,20 +194,20 @@ class SelfAssessmentModule(XModule):
d.update({
'progress_changed': after != before,
'progress_status': Progress.to_js_status_str(after),
- })
+ })
return json.dumps(d, cls=ComplexEncoder)
- def show_rubric(self,get):
+ def show_rubric(self, get):
"""
After the problem is submitted, show the rubric
"""
#Check to see if attempts are less than max
- if(self.attempts
Date: Thu, 1 Nov 2012 14:32:40 -0400
Subject: [PATCH 030/121] reverted capa module after accidental changes
---
common/lib/xmodule/xmodule/capa_module.py | 116 +++++++++++-----------
1 file changed, 58 insertions(+), 58 deletions(-)
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index 46f07796db..151c726f66 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -44,10 +44,10 @@ def only_one(lst, default="", process=lambda x: x):
def parse_timedelta(time_str):
"""
time_str: A string with the following components:
- day[s] (optional)
- hour[s] (optional)
- minute[s] (optional)
- second[s] (optional)
+ day[s] (optional)
+ hour[s] (optional)
+ minute[s] (optional)
+ second[s] (optional)
Returns a datetime.timedelta parsed from the string
"""
@@ -79,7 +79,7 @@ class CapaModule(XModule):
js = {'coffee': [resource_string(__name__, 'js/src/capa/display.coffee'),
resource_string(__name__, 'js/src/collapsible.coffee'),
resource_string(__name__, 'js/src/javascript_loader.coffee'),
- ],
+ ],
'js': [resource_string(__name__, 'js/src/capa/imageinput.js'),
resource_string(__name__, 'js/src/capa/schematic.js')]}
@@ -89,7 +89,7 @@ class CapaModule(XModule):
def __init__(self, system, location, definition, descriptor, instance_state=None,
shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, descriptor, instance_state,
- shared_state, **kwargs)
+ shared_state, **kwargs)
self.attempts = 0
self.max_attempts = None
@@ -100,7 +100,7 @@ class CapaModule(XModule):
if display_due_date_string is not None:
self.display_due_date = dateutil.parser.parse(display_due_date_string)
#log.debug("Parsed " + display_due_date_string +
- # " to " + str(self.display_due_date))
+ # " to " + str(self.display_due_date))
else:
self.display_due_date = None
@@ -109,7 +109,7 @@ class CapaModule(XModule):
self.grace_period = parse_timedelta(grace_period_string)
self.close_date = self.display_due_date + self.grace_period
#log.debug("Then parsed " + grace_period_string +
- # " to closing date" + str(self.close_date))
+ # " to closing date" + str(self.close_date))
else:
self.grace_period = None
self.close_date = self.display_due_date
@@ -137,9 +137,9 @@ class CapaModule(XModule):
elif self.rerandomize == "per_student" and hasattr(self.system, 'id'):
# TODO: This line is badly broken:
# (1) We're passing student ID to xmodule.
- # (2) There aren't bins of students. -- we only want 10 or 20 randomizations, and want to assign students
- # to these bins, and may not want cohorts. So e.g. hash(your-id, problem_id) % num_bins.
- # - analytics really needs small number of bins.
+ # (2) There aren't bins of students. -- we only want 10 or 20 randomizations, and want to assign students
+ # to these bins, and may not want cohorts. So e.g. hash(your-id, problem_id) % num_bins.
+ # - analytics really needs small number of bins.
self.seed = system.id
else:
self.seed = None
@@ -148,7 +148,7 @@ class CapaModule(XModule):
# TODO (vshnayder): move as much as possible of this work and error
# checking to descriptor load time
self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(),
- instance_state, seed=self.seed, system=self.system)
+ instance_state, seed=self.seed, system=self.system)
except Exception as err:
msg = 'cannot create LoncapaProblem {loc}: {err}'.format(
loc=self.location.url(), err=err)
@@ -220,10 +220,10 @@ class CapaModule(XModule):
'element_id': self.location.html_id(),
'id': self.id,
'ajax_url': self.system.ajax_url,
- })
+ })
def get_problem_html(self, encapsulate=True):
- '''Return html for the problem. Adds check, reset, save buttons
+ '''Return html for the problem. Adds check, reset, save buttons
as necessary based on the problem config and state.'''
try:
@@ -242,15 +242,15 @@ class CapaModule(XModule):
html = msg
else:
# We're in non-debug mode, and possibly even in production. We want
- # to avoid bricking of problem as much as possible
+ # to avoid bricking of problem as much as possible
# Presumably, student submission has corrupted LoncapaProblem HTML.
- # First, pull down all student answers
+ # First, pull down all student answers
student_answers = self.lcp.student_answers
answer_ids = student_answers.keys()
# Some inputtypes, such as dynamath, have additional "hidden" state that
- # is not exposed to the student. Keep those hidden
+ # is not exposed to the student. Keep those hidden
# TODO: Use regex, e.g. 'dynamath' is suffix at end of answer_id
hidden_state_keywords = ['dynamath']
for answer_id in answer_ids:
@@ -258,17 +258,17 @@ class CapaModule(XModule):
if answer_id.find(hidden_state_keyword) >= 0:
student_answers.pop(answer_id)
- # Next, generate a fresh LoncapaProblem
+ # Next, generate a fresh LoncapaProblem
self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(),
- state=None, # Tabula rasa
- seed=self.seed, system=self.system)
+ state=None, # Tabula rasa
+ seed=self.seed, system=self.system)
# Prepend a scary warning to the student
- warning = '
'\
- '
Warning: The problem has been reset to its initial state!
'\
- 'The problem\'s state was corrupted by an invalid submission. '\
- 'The submission consisted of:'\
- '
'
+ warning = '
'\
+ '
Warning: The problem has been reset to its initial state!
'\
+ 'The problem\'s state was corrupted by an invalid submission. ' \
+ 'The submission consisted of:'\
+ '
'
for student_answer in student_answers.values():
if student_answer != '':
warning += '
' + cgi.escape(student_answer) + '
'
@@ -292,11 +292,11 @@ class CapaModule(XModule):
# check button is context-specific.
# Put a "Check" button if unlimited attempts or still some left
- if self.max_attempts is None or self.attempts < self.max_attempts-1:
+ if self.max_attempts is None or self.attempts < self.max_attempts-1:
check_button = "Check"
else:
# Will be final check so let user know that
- check_button = "Final Check"
+ check_button = "Final Check"
reset_button = True
save_button = True
@@ -363,8 +363,8 @@ class CapaModule(XModule):
Returns a json dictionary:
{ 'progress_changed' : True/False,
- 'progress' : 'none'/'in_progress'/'done',
- }
+ 'progress' : 'none'/'in_progress'/'done',
+ }
'''
handlers = {
'problem_get': self.get_problem,
@@ -426,10 +426,10 @@ class CapaModule(XModule):
def update_score(self, get):
"""
Delivers grading response (e.g. from asynchronous code checking) to
- the capa problem, so its score can be updated
+ the capa problem, so its score can be updated
'get' must have a field 'response' which is a string that contains the
- grader's response
+ grader's response
No ajax return is needed. Return empty dict.
"""
@@ -437,7 +437,7 @@ class CapaModule(XModule):
score_msg = get['xqueue_body']
self.lcp.update_score(score_msg, queuekey)
- return dict() # No AJAX return is needed
+ return dict() # No AJAX return is needed
def get_answer(self, get):
'''
@@ -453,8 +453,8 @@ class CapaModule(XModule):
else:
answers = self.lcp.get_question_answers()
- # answers (eg ) may have embedded images
- # but be careful, some problems are using non-string answer dicts
+ # answers (eg ) may have embedded images
+ # but be careful, some problems are using non-string answer dicts
new_answers = dict()
for answer_id in answers:
try:
@@ -471,8 +471,8 @@ class CapaModule(XModule):
''' Return results of get_problem_html, as a simple dict for json-ing.
{ 'html': }
- Used if we want to reconfirm we have the right thing e.g. after
- several AJAX calls.
+ Used if we want to reconfirm we have the right thing e.g. after
+ several AJAX calls.
'''
return {'html': self.get_problem_html(encapsulate=False)}
@@ -500,11 +500,11 @@ class CapaModule(XModule):
def check_problem(self, get):
''' Checks whether answers to a problem are correct, and
- returns a map of correct/incorrect answers:
+ returns a map of correct/incorrect answers:
- {'success' : bool,
- 'contents' : html}
- '''
+ {'success' : bool,
+ 'contents' : html}
+ '''
event_info = dict()
event_info['state'] = self.lcp.get_state()
event_info['problem_id'] = self.location.url()
@@ -527,11 +527,11 @@ class CapaModule(XModule):
# Problem queued. Students must wait a specified waittime before they are allowed to submit
if self.lcp.is_queued():
current_time = datetime.datetime.now()
- prev_submit_time = self.lcp.get_recentmost_queuetime()
+ prev_submit_time = self.lcp.get_recentmost_queuetime()
waittime_between_requests = self.system.xqueue['waittime']
if (current_time-prev_submit_time).total_seconds() < waittime_between_requests:
msg = 'You must wait at least %d seconds between submissions' % waittime_between_requests
- return {'success': msg, 'html': ''} # Prompts a modal dialog in ajax callback
+ return {'success': msg, 'html': ''} # Prompts a modal dialog in ajax callback
try:
old_state = self.lcp.get_state()
@@ -540,13 +540,13 @@ class CapaModule(XModule):
except StudentInputError as inst:
# TODO (vshnayder): why is this line here?
#self.lcp = LoncapaProblem(self.definition['data'],
- # id=lcp_id, state=old_state, system=self.system)
+ # id=lcp_id, state=old_state, system=self.system)
log.exception("StudentInputError in capa_module:problem_check")
return {'success': inst.message}
except Exception, err:
# TODO: why is this line here?
#self.lcp = LoncapaProblem(self.definition['data'],
- # id=lcp_id, state=old_state, system=self.system)
+ # id=lcp_id, state=old_state, system=self.system)
if self.system.DEBUG:
msg = "Error checking problem: " + str(err)
msg += '\nTraceback:\n' + traceback.format_exc()
@@ -564,14 +564,14 @@ class CapaModule(XModule):
success = 'incorrect'
# NOTE: We are logging both full grading and queued-grading submissions. In the latter,
- # 'success' will always be incorrect
+ # 'success' will always be incorrect
event_info['correct_map'] = correct_map.get_dict()
event_info['success'] = success
- event_info['attempts'] = self.attempts
+ event_info['attempts'] = self.attempts
self.system.track_function('save_problem_check', event_info)
- if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback
- self.system.psychometrics_handler(self.get_instance_state())
+ if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback
+ self.system.psychometrics_handler(self.get_instance_state())
# render problem into HTML
html = self.get_problem_html(encapsulate=False)
@@ -610,15 +610,15 @@ class CapaModule(XModule):
self.lcp.student_answers = answers
- # TODO: should this be save_problem_fail? Looks like success to me...
+ # TODO: should this be save_problem_fail? Looks like success to me...
self.system.track_function('save_problem_fail', event_info)
return {'success': True}
def reset_problem(self, get):
''' Changes problem state to unfinished -- removes student answers,
- and causes problem to rerender itself.
+ and causes problem to rerender itself.
- Returns problem html as { 'html' : html-string }.
+ Returns problem html as { 'html' : html-string }.
'''
event_info = dict()
event_info['old_state'] = self.lcp.get_state()
@@ -643,8 +643,8 @@ class CapaModule(XModule):
self.lcp.seed = None
self.lcp = LoncapaProblem(self.definition['data'],
- self.location.html_id(), self.lcp.get_state(),
- system=self.system)
+ self.location.html_id(), self.lcp.get_state(),
+ system=self.system)
event_info['new_state'] = self.lcp.get_state()
self.system.track_function('reset_problem', event_info)
@@ -665,7 +665,7 @@ class CapaDescriptor(RawDescriptor):
template_dir_name = 'problem'
# Capa modules have some additional metadata:
- # TODO (vshnayder): do problems have any other metadata? Do they
+ # TODO (vshnayder): do problems have any other metadata? Do they
# actually use type and points?
metadata_attributes = RawDescriptor.metadata_attributes + ('type', 'points')
@@ -677,13 +677,13 @@ class CapaDescriptor(RawDescriptor):
return [
'problems/' + path[8:],
path[8:],
- ]
-
+ ]
+
def __init__(self, *args, **kwargs):
super(CapaDescriptor, self).__init__(*args, **kwargs)
-
+
weight_string = self.metadata.get('weight', None)
if weight_string:
self.weight = float(weight_string)
else:
- self.weight = None
\ No newline at end of file
+ self.weight = None
From e97af7cf59186454c853b498fdb6dfc7f9128aa6 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Thu, 1 Nov 2012 14:37:47 -0400
Subject: [PATCH 031/121] edit javascript var name
---
.../lib/xmodule/xmodule/js/src/selfassessment/display.coffee | 4 ++--
common/lib/xmodule/xmodule/self_assessment_module.py | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
index b1a10ef411..e81dc26741 100644
--- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
@@ -13,10 +13,10 @@ $(document).on('click', 'section.sa-wrapper input#show', ( ->
));
$(document).on('click', 'section.sa-wrapper input#save', ( ->
- assessment_correct=$('section.sa-wrapper #assessment').find(':selected').text()
+ assessment=$('section.sa-wrapper #assessment').find(':selected').text()
post_url=$('section.sa-wrapper input#save').attr('url')
final_url="#{post_url}/sa_save"
- $.post final_url, assessment_correct, (response) ->
+ $.post final_url, assessment, (response) ->
if response.success
$('section.sa-wrapper p#save_message').append(response.message)
$('section.sa-wrapper input#save').remove()
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index 003d3bf36f..fdef2df54a 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -270,7 +270,7 @@ class SelfAssessmentModule(XModule):
class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
"""
- Module for putting raw html in a course
+ Module for adding self assessment questions to courses
"""
mako_template = "widgets/html-edit.html"
module_class = SelfAssessmentModule
From f8625660fea328cf631429e922dc6fbc6d7591d3 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Thu, 1 Nov 2012 14:52:37 -0400
Subject: [PATCH 032/121] add previous answer display
---
.../xmodule/xmodule/self_assessment_module.py | 83 ++++++++++---------
1 file changed, 43 insertions(+), 40 deletions(-)
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index fdef2df54a..f5bb3c3d67 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -72,49 +72,18 @@ class SelfAssessmentModule(XModule):
Sample file:
-
- Insert problem text here.
-
-
- Insert grading rubric here.
-
-
- Thanks for submitting!
-
+
+ Insert problem text here.
+
+
+ Insert grading rubric here.
+
+
+ Thanks for submitting!
+
-
-
"""
- #Parse definition file
- dom2 = etree.fromstring("" + self.definition['data'] + "")
-
- #Extract problem, submission message and rubric from definition file
- self.rubric = "
" + ''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))])
- self.problem = ''.join([etree.tostring(child) for child in only_one(dom2.xpath('problem'))])
- self.submit_message = etree.tostring(dom2.xpath('submitmessage')[0])
-
- #Forms to append to problem and rubric that capture student responses.
- #Do not change ids and names, as javascript (selfassessment/display.coffee) depends on them
- problem_form = (' '
- ''
- '
').format(system.ajax_url)
-
- rubric_form = ('
Please assess your performance given the above rubric: '
- ' '
- ''
- '
').format(system.ajax_url)
-
- #Combine problem, rubric, and the forms
- self.problem = ''.join([self.problem, problem_form])
- self.rubric = ''.join([self.rubric, rubric_form])
-
- #Display the problem to the student to begin with
- self.html = self.problem
-
#Initialize variables
self.answer = ""
self.score = 0
@@ -148,6 +117,40 @@ class SelfAssessmentModule(XModule):
self.score = instance_state['correct_map']['self_assess']['npoints']
self.correctness = instance_state['correct_map']['self_assess']['correctness']
+ #Parse definition file
+ dom2 = etree.fromstring("" + self.definition['data'] + "")
+
+ #Extract problem, submission message and rubric from definition file
+ self.rubric = "
" + ''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))])
+ self.problem = ''.join([etree.tostring(child) for child in only_one(dom2.xpath('problem'))])
+ self.submit_message = etree.tostring(dom2.xpath('submitmessage')[0])
+
+ #Forms to append to problem and rubric that capture student responses.
+ #Do not change ids and names, as javascript (selfassessment/display.coffee) depends on them
+ problem_form = (' '
+ ''
+ '
').format(system.ajax_url)
+
+ rubric_form = ('
Please assess your performance given the above rubric: '
+ ' '
+ ''
+ '
').format(system.ajax_url)
+
+ #Combine problem, rubric, and the forms
+ if self.answer is not "" :
+ answer_html="
Previous Answer: {0}
".format(self.answer)
+ self.problem = ''.join([self.problem, answer_html, problem_form])
+ else:
+ self.problem = ''.join([self.problem, problem_form])
+
+ self.rubric = ''.join([self.rubric, rubric_form])
+
+ #Display the problem to the student to begin with
+ self.html = self.problem
+
def get_score(self):
return {'score': self.score}
From e43467377323babfbab09e298e28e6efc5bd145e Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Thu, 1 Nov 2012 14:57:53 -0400
Subject: [PATCH 033/121] moved ajax url to hidden input types
---
.../xmodule/js/src/selfassessment/display.coffee | 4 ++--
common/lib/xmodule/xmodule/self_assessment_module.py | 11 +++++++----
2 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
index e81dc26741..a49d6feb66 100644
--- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
@@ -1,5 +1,5 @@
$(document).on('click', 'section.sa-wrapper input#show', ( ->
- post_url=$('section.sa-wrapper input#show').attr('url')
+ post_url=$('section.sa-wrapper input#ajax_url').attr('url')
final_url="#{post_url}/sa_show"
answer=$('section.sa-wrapper textarea#answer').val()
$.post final_url, answer, (response) ->
@@ -14,7 +14,7 @@ $(document).on('click', 'section.sa-wrapper input#show', ( ->
$(document).on('click', 'section.sa-wrapper input#save', ( ->
assessment=$('section.sa-wrapper #assessment').find(':selected').text()
- post_url=$('section.sa-wrapper input#save').attr('url')
+ post_url=$('section.sa-wrapper input#ajax_url').attr('url')
final_url="#{post_url}/sa_save"
$.post final_url, assessment, (response) ->
if response.success
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index f5bb3c3d67..6bf9820bdf 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -129,15 +129,18 @@ class SelfAssessmentModule(XModule):
#Do not change ids and names, as javascript (selfassessment/display.coffee) depends on them
problem_form = (' '
- ''
- '
').format(system.ajax_url)
+ ''
+ '
').format(system.ajax_url)
rubric_form = ('
Please assess your performance given the above rubric: '
' '
- ''
- '
').format(system.ajax_url)
+ ''
+ ''
+ '
').format(system.ajax_url)
#Combine problem, rubric, and the forms
if self.answer is not "" :
From 99b0795e59ef2f3943285d38fa1f3cc4f1facc28 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Thu, 1 Nov 2012 15:10:05 -0400
Subject: [PATCH 034/121] replaced removed file from master
---
lms/static/admin/css/ie.css | 63 +++++++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)
create mode 100644 lms/static/admin/css/ie.css
diff --git a/lms/static/admin/css/ie.css b/lms/static/admin/css/ie.css
new file mode 100644
index 0000000000..fd00f7f204
--- /dev/null
+++ b/lms/static/admin/css/ie.css
@@ -0,0 +1,63 @@
+/* IE 6 & 7 */
+
+/* Proper fixed width for dashboard in IE6 */
+
+.dashboard #content {
+ *width: 768px;
+}
+
+.dashboard #content-main {
+ *width: 535px;
+}
+
+/* IE 6 ONLY */
+
+/* Keep header from flowing off the page */
+
+#container {
+ _position: static;
+}
+
+/* Put the right sidebars back on the page */
+
+.colMS #content-related {
+ _margin-right: 0;
+ _margin-left: 10px;
+ _position: static;
+}
+
+/* Put the left sidebars back on the page */
+
+.colSM #content-related {
+ _margin-right: 10px;
+ _margin-left: -115px;
+ _position: static;
+}
+
+.form-row {
+ _height: 1%;
+}
+
+/* Fix right margin for changelist filters in IE6 */
+
+#changelist-filter ul {
+ _margin-right: -10px;
+}
+
+/* IE ignores min-height, but treats height as if it were min-height */
+
+.change-list .filtered {
+ _height: 400px;
+}
+
+/* IE doesn't know alpha transparency in PNGs */
+
+.inline-deletelink {
+ background: transparent url(../img/inline-delete-8bit.png) no-repeat;
+}
+
+/* IE7 doesn't support inline-block */
+.change-list ul.toplinks li {
+ zoom: 1;
+ *display: inline;
+}
\ No newline at end of file
From ec0f8dacfe3ad4d75881fb2337070e3442360c3a Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Thu, 1 Nov 2012 17:45:58 -0400
Subject: [PATCH 035/121] clean up display code
---
common/lib/xmodule/xmodule/html_module.py | 2 --
.../xmodule/js/src/selfassessment/display.coffee | 2 +-
.../lib/xmodule/xmodule/self_assessment_module.py | 14 ++++++++------
3 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/common/lib/xmodule/xmodule/html_module.py b/common/lib/xmodule/xmodule/html_module.py
index 2023ac7017..6145a3b0ab 100644
--- a/common/lib/xmodule/xmodule/html_module.py
+++ b/common/lib/xmodule/xmodule/html_module.py
@@ -38,8 +38,6 @@ class HtmlModule(XModule):
instance_state, shared_state, **kwargs)
self.html = self.definition['data']
-
-
class HtmlDescriptor(XmlDescriptor, EditingDescriptor):
"""
Module for putting raw html in a course
diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
index a49d6feb66..d08050a94d 100644
--- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
@@ -6,7 +6,7 @@ $(document).on('click', 'section.sa-wrapper input#show', ( ->
if response.success
$('section.sa-wrapper input#show').remove()
$('section.sa-wrapper textarea#answer').remove()
- $('section.sa-wrapper p#rubric').append(answer)
+ $('section.sa-wrapper p#rubric').append("Your answer: #{answer}")
$('section.sa-wrapper p#rubric').append(response.rubric)
else
$('section.sa-wrapper p#rubric').append(response.message)
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index 6bf9820bdf..d274f593bb 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -121,7 +121,7 @@ class SelfAssessmentModule(XModule):
dom2 = etree.fromstring("" + self.definition['data'] + "")
#Extract problem, submission message and rubric from definition file
- self.rubric = "
" + ''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))])
+ self.rubric = " " + ''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))])
self.problem = ''.join([etree.tostring(child) for child in only_one(dom2.xpath('problem'))])
self.submit_message = etree.tostring(dom2.xpath('submitmessage')[0])
@@ -133,8 +133,8 @@ class SelfAssessmentModule(XModule):
'
').format(system.ajax_url)
- rubric_form = ('
Please assess your performance given the above rubric: '
- '
- What hint about this problem would you give to someone?
+ ${ hint_prompt }
From 2aeb6150d326ce9b3135d3bde7739dbf7589535a Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Mon, 5 Nov 2012 19:18:04 -0500
Subject: [PATCH 052/121] Work on documentation
---
.../xmodule/xmodule/self_assessment_module.py | 16 ++++------------
1 file changed, 4 insertions(+), 12 deletions(-)
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index fb6538651e..edf9cc44d9 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -1,6 +1,8 @@
"""
Add Self Assessment module so students can write essay, submit, then see a rubric and rate themselves.
Incredibly hacky solution to persist state and properly display information
+
+TODO: Add some tests
"""
import copy
@@ -84,7 +86,6 @@ class SelfAssessmentModule(XModule):
log.debug('Instance state of self-assessment module {0}: {1}'.format(location.url(), instance_state))
# Pull out state, or initialize variables
-
# lists of student answers, correctness responses ('incorrect'/'correct'), and suggested hints
self.student_answers = instance_state.get('student_answers', [])
self.correctness = instance_state.get('correctness', [])
@@ -221,7 +222,6 @@ class SelfAssessmentModule(XModule):
self.done = True
self.attempts = self.attempts + 1
- # TODO: simplify tracking info to just log the relevant stuff
event_info = dict()
event_info['state'] = {
'student_answers': self.student_answers,
@@ -242,16 +242,8 @@ class SelfAssessmentModule(XModule):
"""
Get the current correctness, points, and done status
"""
- #Assign points based on completion
+ #Assign points based on completion. May want to change to correctness-based down the road.
points = 1
- #This is a pointless if structure, but left in place in case points change from
- #being completion based to correctness based
-
- # TODO: clean up
- if type(self.correctness)==type([]):
- if(len(self.correctness)>0):
- if self.correctness[len(self.correctness)-1]== "correct":
- points = 1
state = {
'student_answers': self.student_answers,
@@ -321,4 +313,4 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
for child in ['rubric', 'prompt', 'submitmessage', 'hintprompt']:
add_child(child)
- return elt
+ return elt
\ No newline at end of file
From 6cdc41505eaf7fce63c16ada4abc0b7d254a4a97 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Tue, 6 Nov 2012 08:50:06 -0500
Subject: [PATCH 053/121] Fixed incorrect dev.py commit
---
lms/envs/dev.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lms/envs/dev.py b/lms/envs/dev.py
index 357e25842b..9114f099d4 100644
--- a/lms/envs/dev.py
+++ b/lms/envs/dev.py
@@ -63,10 +63,10 @@ CACHES = {
XQUEUE_INTERFACE = {
- "url": 'http://127.0.0.1:3031',
+ "url": "https://sandbox-xqueue.edx.org",
"django_auth": {
"username": "lms",
- "password": "abcd"
+ "password": "***REMOVED***"
},
"basic_auth": ('anant', 'agarwal'),
}
From b209018c462e871e0db58a1da2bbf470a9fe347b Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Tue, 6 Nov 2012 09:03:59 -0500
Subject: [PATCH 054/121] Adding tests
---
.../lib/xmodule/xmodule/tests/test_import.py | 7 ++++++
common/test/data/self_assessment/README.md | 1 +
common/test/data/self_assessment/course.xml | 1 +
.../data/self_assessment/course/2012_Fall.xml | 6 +++++
.../data/self_assessment/html/Welcome.xml | 7 ++++++
.../self_assessment/policies/2012_Fall.json | 24 +++++++++++++++++++
.../data/self_assessment/roots/2012_Fall.xml | 1 +
.../selfassessment/SampleQuestion.xml | 14 +++++++++++
8 files changed, 61 insertions(+)
create mode 100644 common/test/data/self_assessment/README.md
create mode 120000 common/test/data/self_assessment/course.xml
create mode 100644 common/test/data/self_assessment/course/2012_Fall.xml
create mode 100644 common/test/data/self_assessment/html/Welcome.xml
create mode 100644 common/test/data/self_assessment/policies/2012_Fall.json
create mode 100644 common/test/data/self_assessment/roots/2012_Fall.xml
create mode 100644 common/test/data/self_assessment/selfassessment/SampleQuestion.xml
diff --git a/common/lib/xmodule/xmodule/tests/test_import.py b/common/lib/xmodule/xmodule/tests/test_import.py
index 21dd8968f6..0db01ac90f 100644
--- a/common/lib/xmodule/xmodule/tests/test_import.py
+++ b/common/lib/xmodule/xmodule/tests/test_import.py
@@ -311,3 +311,10 @@ class ImportTestCase(unittest.TestCase):
system = self.get_system(False)
self.assertRaises(etree.XMLSyntaxError, system.process_xml, bad_xml)
+
+ def test_selfassessment_import_export(self):
+ '''
+ Check to see if definition_from_xml and definition_to_xml in self_assessment_module.py function.
+ '''
+
+
diff --git a/common/test/data/self_assessment/README.md b/common/test/data/self_assessment/README.md
new file mode 100644
index 0000000000..7fe58ac17f
--- /dev/null
+++ b/common/test/data/self_assessment/README.md
@@ -0,0 +1 @@
+This is a very very simple course, useful for debugging self assessment code.
diff --git a/common/test/data/self_assessment/course.xml b/common/test/data/self_assessment/course.xml
new file mode 120000
index 0000000000..49041310f6
--- /dev/null
+++ b/common/test/data/self_assessment/course.xml
@@ -0,0 +1 @@
+roots/2012_Fall.xml
\ No newline at end of file
diff --git a/common/test/data/self_assessment/course/2012_Fall.xml b/common/test/data/self_assessment/course/2012_Fall.xml
new file mode 100644
index 0000000000..18c6abd45b
--- /dev/null
+++ b/common/test/data/self_assessment/course/2012_Fall.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/common/test/data/self_assessment/html/Welcome.xml b/common/test/data/self_assessment/html/Welcome.xml
new file mode 100644
index 0000000000..a623152d16
--- /dev/null
+++ b/common/test/data/self_assessment/html/Welcome.xml
@@ -0,0 +1,7 @@
+
+
+
+
+ Hello and welcome to the test course for self assessment.
+
+
diff --git a/common/test/data/self_assessment/policies/2012_Fall.json b/common/test/data/self_assessment/policies/2012_Fall.json
new file mode 100644
index 0000000000..23da0a6d96
--- /dev/null
+++ b/common/test/data/self_assessment/policies/2012_Fall.json
@@ -0,0 +1,24 @@
+{
+ "course/2012_Fall": {
+ "graceperiod": "2 days 5 hours 59 minutes 59 seconds",
+ "start": "2015-07-17T12:00",
+ "display_name": "Toy Course",
+ "graded": "true"
+ },
+ "chapter/Overview": {
+ "display_name": "Overview"
+ },
+ "videosequence/Toy_Videos": {
+ "display_name": "Toy Videos",
+ "format": "Lecture Sequence"
+ },
+ "html/secret:toylab": {
+ "display_name": "Toy lab"
+ },
+ "video/Video_Resources": {
+ "display_name": "Video Resources"
+ },
+ "video/Welcome": {
+ "display_name": "Welcome"
+ }
+}
diff --git a/common/test/data/self_assessment/roots/2012_Fall.xml b/common/test/data/self_assessment/roots/2012_Fall.xml
new file mode 100644
index 0000000000..ea7d5c420d
--- /dev/null
+++ b/common/test/data/self_assessment/roots/2012_Fall.xml
@@ -0,0 +1 @@
+
diff --git a/common/test/data/self_assessment/selfassessment/SampleQuestion.xml b/common/test/data/self_assessment/selfassessment/SampleQuestion.xml
new file mode 100644
index 0000000000..365862ffcb
--- /dev/null
+++ b/common/test/data/self_assessment/selfassessment/SampleQuestion.xml
@@ -0,0 +1,14 @@
+
+
+ What is the meaning of life?
+
+
+ This is a rubric.
+
+
+ Thanks for your submission!
+
+
+ Enter a hint below:
+
+
From 71547e29da332106212456ec9b22dfd0806f1aa7 Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Tue, 6 Nov 2012 09:17:35 -0500
Subject: [PATCH 055/121] Added tests for selfassessment import and export
(both pass)
---
common/lib/xmodule/xmodule/tests/test_export.py | 3 +++
common/lib/xmodule/xmodule/tests/test_import.py | 7 ++++++-
common/test/data/self_assessment/course/2012_Fall.xml | 1 -
common/test/data/self_assessment/html/Welcome.xml | 7 -------
.../data/self_assessment/selfassessment/SampleQuestion.xml | 4 ++--
5 files changed, 11 insertions(+), 11 deletions(-)
delete mode 100644 common/test/data/self_assessment/html/Welcome.xml
diff --git a/common/lib/xmodule/xmodule/tests/test_export.py b/common/lib/xmodule/xmodule/tests/test_export.py
index 826e6c9d5a..de3aeda77c 100644
--- a/common/lib/xmodule/xmodule/tests/test_export.py
+++ b/common/lib/xmodule/xmodule/tests/test_export.py
@@ -113,3 +113,6 @@ class RoundTripTestCase(unittest.TestCase):
def test_full_roundtrip(self):
self.check_export_roundtrip(DATA_DIR, "full")
+
+ def test_selfassessment_roundtrip(self):
+ self.check_export_roundtrip(DATA_DIR,"self_assessment")
diff --git a/common/lib/xmodule/xmodule/tests/test_import.py b/common/lib/xmodule/xmodule/tests/test_import.py
index 0db01ac90f..34ea8da537 100644
--- a/common/lib/xmodule/xmodule/tests/test_import.py
+++ b/common/lib/xmodule/xmodule/tests/test_import.py
@@ -312,9 +312,14 @@ class ImportTestCase(unittest.TestCase):
self.assertRaises(etree.XMLSyntaxError, system.process_xml, bad_xml)
- def test_selfassessment_import_export(self):
+ def test_selfassessment_import(self):
'''
Check to see if definition_from_xml and definition_to_xml in self_assessment_module.py function.
'''
+ modulestore = XMLModuleStore(DATA_DIR, course_dirs=['self_assessment'])
+ sa_id = "edX/sa_test/2012_Fall"
+ location = Location(["i4x", "edX", "sa_test", "selfassessment", "SampleQuestion"])
+ sa_sample = modulestore.get_instance(sa_id, location)
+ self.assertEqual(sa_sample.metadata['attempts'], '10')
\ No newline at end of file
diff --git a/common/test/data/self_assessment/course/2012_Fall.xml b/common/test/data/self_assessment/course/2012_Fall.xml
index 18c6abd45b..bbbfb8b900 100644
--- a/common/test/data/self_assessment/course/2012_Fall.xml
+++ b/common/test/data/self_assessment/course/2012_Fall.xml
@@ -1,6 +1,5 @@
-
diff --git a/common/test/data/self_assessment/html/Welcome.xml b/common/test/data/self_assessment/html/Welcome.xml
deleted file mode 100644
index a623152d16..0000000000
--- a/common/test/data/self_assessment/html/Welcome.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
- Hello and welcome to the test course for self assessment.
-
-
diff --git a/common/test/data/self_assessment/selfassessment/SampleQuestion.xml b/common/test/data/self_assessment/selfassessment/SampleQuestion.xml
index 365862ffcb..6c383763b1 100644
--- a/common/test/data/self_assessment/selfassessment/SampleQuestion.xml
+++ b/common/test/data/self_assessment/selfassessment/SampleQuestion.xml
@@ -8,7 +8,7 @@
Thanks for your submission!
-
+
Enter a hint below:
-
+
From fee0facd39211f8c5de105e7479611bfc11a656b Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Tue, 6 Nov 2012 09:30:59 -0500
Subject: [PATCH 056/121] Add in some docs for tests, factor out class name in
js
---
.../js/src/selfassessment/display.coffee | 32 ++++++++++---------
.../lib/xmodule/xmodule/tests/test_export.py | 1 +
.../lib/xmodule/xmodule/tests/test_import.py | 4 ++-
.../self_assessment/policies/2012_Fall.json | 16 ++--------
4 files changed, 24 insertions(+), 29 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
index 99ccccb6b6..566131cd79 100644
--- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
@@ -1,25 +1,27 @@
-$(document).on('click', 'section.sa-wrapper input#show', ( ->
- post_url=$('section.sa-wrapper input#ajax_url').attr('url')
+wrapper_name='section.sa-wrapper'
+
+$(document).on('click', "#{wrapper_name} input#show", ( ->
+ post_url=$("#{wrapper_name} input#ajax_url").attr('url')
final_url="#{post_url}/sa_show"
- answer=$('section.sa-wrapper textarea#answer').val()
+ answer=$("#{wrapper_name} textarea#answer").val()
$.post final_url, {'student_answer' : answer }, (response) ->
if response.success
- $('section.sa-wrapper input#show').remove()
- $('section.sa-wrapper textarea#answer').remove()
- $('section.sa-wrapper p#rubric').append("Your answer: #{answer}")
- $('section.sa-wrapper p#rubric').append(response.rubric)
+ $("#{wrapper_name} input#show").remove()
+ $("#{wrapper_name} textarea#answer").remove()
+ $("#{wrapper_name} p#rubric").append("Your answer: #{answer}")
+ $("#{wrapper_name} p#rubric").append(response.rubric)
else
- $('section.sa-wrapper input#show').remove()
- $('section.sa-wrapper p#rubric').append(response.message)
+ $("#{wrapper_name} input#show").remove()
+ $("#{wrapper_name} p#rubric").append(response.message)
));
-$(document).on('click', 'section.sa-wrapper input#save', ( ->
- assessment=$('section.sa-wrapper #assessment').find(':selected').text()
- post_url=$('section.sa-wrapper input#ajax_url').attr('url')
+$(document).on('click', "#{wrapper_name} input#save", ( ->
+ assessment=$("#{wrapper_name} #assessment").find(':selected').text()
+ post_url=$("#{wrapper_name} input#ajax_url").attr('url')
final_url="#{post_url}/sa_save"
- hint=$('section.sa-wrapper textarea#hint').val()
+ hint=$("#{wrapper_name} textarea#hint").val()
$.post final_url, {'assessment':assessment, 'hint':hint}, (response) ->
if response.success
- $('section.sa-wrapper p#save_message').append(response.message)
- $('section.sa-wrapper input#save').remove()
+ $("#{wrapper_name} p#save_message").append(response.message)
+ $("#{wrapper_name} input#save").remove()
));
diff --git a/common/lib/xmodule/xmodule/tests/test_export.py b/common/lib/xmodule/xmodule/tests/test_export.py
index de3aeda77c..aeebc6da6b 100644
--- a/common/lib/xmodule/xmodule/tests/test_export.py
+++ b/common/lib/xmodule/xmodule/tests/test_export.py
@@ -115,4 +115,5 @@ class RoundTripTestCase(unittest.TestCase):
self.check_export_roundtrip(DATA_DIR, "full")
def test_selfassessment_roundtrip(self):
+ #Test selfassessment xmodule to see if it exports correctly
self.check_export_roundtrip(DATA_DIR,"self_assessment")
diff --git a/common/lib/xmodule/xmodule/tests/test_import.py b/common/lib/xmodule/xmodule/tests/test_import.py
index 34ea8da537..9e8199629a 100644
--- a/common/lib/xmodule/xmodule/tests/test_import.py
+++ b/common/lib/xmodule/xmodule/tests/test_import.py
@@ -314,7 +314,8 @@ class ImportTestCase(unittest.TestCase):
def test_selfassessment_import(self):
'''
- Check to see if definition_from_xml and definition_to_xml in self_assessment_module.py function.
+ Check to see if definition_from_xml in self_assessment_module.py
+ works properly. Pulls data from the self_assessment directory in the test data directory.
'''
modulestore = XMLModuleStore(DATA_DIR, course_dirs=['self_assessment'])
@@ -322,4 +323,5 @@ class ImportTestCase(unittest.TestCase):
sa_id = "edX/sa_test/2012_Fall"
location = Location(["i4x", "edX", "sa_test", "selfassessment", "SampleQuestion"])
sa_sample = modulestore.get_instance(sa_id, location)
+ #10 attempts is hard coded into SampleQuestion, which is the url_name of a selfassessment xml tag
self.assertEqual(sa_sample.metadata['attempts'], '10')
\ No newline at end of file
diff --git a/common/test/data/self_assessment/policies/2012_Fall.json b/common/test/data/self_assessment/policies/2012_Fall.json
index 23da0a6d96..aae4670296 100644
--- a/common/test/data/self_assessment/policies/2012_Fall.json
+++ b/common/test/data/self_assessment/policies/2012_Fall.json
@@ -2,23 +2,13 @@
"course/2012_Fall": {
"graceperiod": "2 days 5 hours 59 minutes 59 seconds",
"start": "2015-07-17T12:00",
- "display_name": "Toy Course",
+ "display_name": "Self Assessment Test",
"graded": "true"
},
"chapter/Overview": {
"display_name": "Overview"
},
- "videosequence/Toy_Videos": {
- "display_name": "Toy Videos",
- "format": "Lecture Sequence"
+ "selfassessment/SampleQuestion": {
+ "display_name": "Sample Question",
},
- "html/secret:toylab": {
- "display_name": "Toy lab"
- },
- "video/Video_Resources": {
- "display_name": "Video Resources"
- },
- "video/Welcome": {
- "display_name": "Welcome"
- }
}
From b66a559b1dc441b8f56b3402ed5e065da737065e Mon Sep 17 00:00:00 2001
From: Vik Paruchuri
Date: Tue, 6 Nov 2012 09:49:00 -0500
Subject: [PATCH 057/121] Add some more docs to code, remove some old code that
assigned points.
---
.../js/src/selfassessment/display.coffee | 32 +++++++++----------
.../xmodule/xmodule/self_assessment_module.py | 32 ++++++++++---------
2 files changed, 33 insertions(+), 31 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
index 566131cd79..0fdc3ab357 100644
--- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
@@ -1,27 +1,27 @@
-wrapper_name='section.sa-wrapper'
+sa_wrapper_name='section.sa-wrapper'
-$(document).on('click', "#{wrapper_name} input#show", ( ->
- post_url=$("#{wrapper_name} input#ajax_url").attr('url')
+$(document).on('click', "#{sa_wrapper_name} input#show", ( ->
+ post_url=$("#{sa_wrapper_name} input#ajax_url").attr('url')
final_url="#{post_url}/sa_show"
- answer=$("#{wrapper_name} textarea#answer").val()
+ answer=$("#{sa_wrapper_name} textarea#answer").val()
$.post final_url, {'student_answer' : answer }, (response) ->
if response.success
- $("#{wrapper_name} input#show").remove()
- $("#{wrapper_name} textarea#answer").remove()
- $("#{wrapper_name} p#rubric").append("Your answer: #{answer}")
- $("#{wrapper_name} p#rubric").append(response.rubric)
+ $("#{sa_wrapper_name} input#show").remove()
+ $("#{sa_wrapper_name} textarea#answer").remove()
+ $("#{sa_wrapper_name} p#rubric").append("Your answer: #{answer}")
+ $("#{sa_wrapper_name} p#rubric").append(response.rubric)
else
- $("#{wrapper_name} input#show").remove()
- $("#{wrapper_name} p#rubric").append(response.message)
+ $("#{sa_wrapper_name} input#show").remove()
+ $("#{sa_wrapper_name} p#rubric").append(response.message)
));
-$(document).on('click', "#{wrapper_name} input#save", ( ->
- assessment=$("#{wrapper_name} #assessment").find(':selected').text()
- post_url=$("#{wrapper_name} input#ajax_url").attr('url')
+$(document).on('click', "#{sa_wrapper_name} input#save", ( ->
+ assessment=$("#{sa_wrapper_name} #assessment").find(':selected').text()
+ post_url=$("#{sa_wrapper_name} input#ajax_url").attr('url')
final_url="#{post_url}/sa_save"
- hint=$("#{wrapper_name} textarea#hint").val()
+ hint=$("#{sa_wrapper_name} textarea#hint").val()
$.post final_url, {'assessment':assessment, 'hint':hint}, (response) ->
if response.success
- $("#{wrapper_name} p#save_message").append(response.message)
- $("#{wrapper_name} input#save").remove()
+ $("#{sa_wrapper_name} p#save_message").append(response.message)
+ $("#{sa_wrapper_name} input#save").remove()
));
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index edf9cc44d9..193eb5a8d2 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -1,8 +1,7 @@
"""
Add Self Assessment module so students can write essay, submit, then see a rubric and rate themselves.
-Incredibly hacky solution to persist state and properly display information
-
-TODO: Add some tests
+Persists student supplied hints, answers, and correctness judgment (currently only correct/incorrect).
+Parses xml definition file--see below for exact format.
"""
import copy
@@ -103,6 +102,7 @@ class SelfAssessmentModule(XModule):
# TODO: do we need this? True once everything is done
self.done = instance_state.get('done', False)
+ #Get number of attempts student has used from instance state
self.attempts = instance_state.get('attempts', 0)
#Try setting maxattempts, use default if not available in metadata
@@ -114,11 +114,13 @@ class SelfAssessmentModule(XModule):
self.submit_message = definition['submitmessage']
self.hint_prompt = definition['hintprompt']
- #set context variables and render template
+ #Determine if student has answered the question before. This is used to display
+ #a "previous answer" message to the student if they have.
previous_answer=''
if len(self.student_answers)>0:
previous_answer=self.student_answers[len(self.student_answers)-1]
+ #set context variables and render template
self.context = {
'prompt' : self.prompt,
'rubric' : self.rubric,
@@ -184,16 +186,16 @@ class SelfAssessmentModule(XModule):
"""
#Check to see if attempts are less than max
if(self.attempts < self.max_attempts):
- # Dump to temp to keep answer in sync with correctness and hint
-
- # TODO: expecting something like get['answer']
+ # Dump to temp_answer to keep answer in sync with correctness and hint
self.temp_answer = get['student_answer']
- log.debug(self.temp_answer)
+
+ #Return success and return rubric html to ajax call
return {
'success': True,
'rubric': self.system.render_template('self_assessment_rubric.html', self.context)
}
else:
+ #If too many attempts, prevent student from saving answer and seeing rubric.
return{
'success': False,
'message': 'Too many attempts.'
@@ -207,21 +209,19 @@ class SelfAssessmentModule(XModule):
'''
#Temp answer check is to keep hints, correctness, and answer in sync
- points = 0
- log.debug(self.temp_answer)
if self.temp_answer is not "":
- #Extract correctness and hint from ajax and assign points
+ #Extract correctness and hint from ajax, and add temp answer to student answers
self.hints.append(get['hint'])
- curr_correctness = get['assessment'].lower()
- if curr_correctness == "correct":
- points = 1
- self.correctness.append(curr_correctness)
+ self.correctness.append(get['assessment'].lower())
self.student_answers.append(self.temp_answer)
#Student is done, and increment attempts
self.done = True
self.attempts = self.attempts + 1
+ #Create and store event info dict
+ #Currently points are assigned for completion, so set to 1 instead of depending on correctness.
+ points=1
event_info = dict()
event_info['state'] = {
'student_answers': self.student_answers,
@@ -236,6 +236,7 @@ class SelfAssessmentModule(XModule):
self.system.track_function('save_problem_succeed', event_info)
+ #Return the submitmessage specified in xml defintion on success
return {'success': True, 'message': self.submit_message}
def get_instance_state(self):
@@ -283,6 +284,7 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
'rubric' : 'some-html',
'prompt' : 'some-html',
'submitmessage' : 'some-html'
+ 'hintprompt' : 'some-html'
}
"""
expected_children = ['rubric', 'prompt', 'submitmessage', 'hintprompt']
From e3c213dd388738cbe2a73afa6e2eae253cc90883 Mon Sep 17 00:00:00 2001
From: Victor Shnayder
Date: Wed, 7 Nov 2012 18:18:35 -0500
Subject: [PATCH 058/121] Refactor html and js.
Next:
- more clear states (logic in python, view in js)
- style
---
.../js/src/selfassessment/display.coffee | 69 ++++++++++++-------
.../xmodule/xmodule/self_assessment_module.py | 68 +++++++++---------
lms/templates/self_assessment_prompt.html | 25 +++----
lms/templates/self_assessment_rubric.html | 29 ++++----
4 files changed, 105 insertions(+), 86 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
index 0fdc3ab357..e6255ae9e8 100644
--- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee
@@ -1,27 +1,46 @@
-sa_wrapper_name='section.sa-wrapper'
+class @SelfAssessment
+ constructor: (element) ->
+ @el = $(element).find('section.self-assessment')
+ @id = @el.data('id')
+ @ajax_url = @el.data('ajax-url')
-$(document).on('click', "#{sa_wrapper_name} input#show", ( ->
- post_url=$("#{sa_wrapper_name} input#ajax_url").attr('url')
- final_url="#{post_url}/sa_show"
- answer=$("#{sa_wrapper_name} textarea#answer").val()
- $.post final_url, {'student_answer' : answer }, (response) ->
- if response.success
- $("#{sa_wrapper_name} input#show").remove()
- $("#{sa_wrapper_name} textarea#answer").remove()
- $("#{sa_wrapper_name} p#rubric").append("Your answer: #{answer}")
- $("#{sa_wrapper_name} p#rubric").append(response.rubric)
+ # Where to put the rubric once we load it
+ @rubric_wrapper = @$('.rubric-wrapper')
+ @check_button = @$('.submit-button')
+ @answer_area = @$('textarea.answer')
+ @errors_area = @$('.error')
+ @state = 'prompt' # switches to 'eval' after answer is submitted
+ @bind()
+
+ # locally scoped jquery.
+ $: (selector) ->
+ $(selector, @el)
+
+ bind: ->
+ @check_button.click @show_rubric
+
+ find_eval_elements: ->
+ # find the elements we'll need from the newly loaded rubric data
+ @assessment = @$('select.assessment')
+ @hint = @$('textarea.hint')
+ @save_message = @$('.save_message')
+
+ show_rubric: (event) =>
+ event.preventDefault()
+ if @state == 'prompt'
+ data = {'student_answer' : @answer_area.val()}
+ $.postWithPrefix "#{@ajax_url}/show", data, (response) =>
+ if response.success
+ @rubric_wrapper.html(response.rubric)
+ @state = 'eval'
+ @find_eval_elements()
+ else
+ @errors_area.html(response.message)
else
- $("#{sa_wrapper_name} input#show").remove()
- $("#{sa_wrapper_name} p#rubric").append(response.message)
-));
-
-$(document).on('click', "#{sa_wrapper_name} input#save", ( ->
- assessment=$("#{sa_wrapper_name} #assessment").find(':selected').text()
- post_url=$("#{sa_wrapper_name} input#ajax_url").attr('url')
- final_url="#{post_url}/sa_save"
- hint=$("#{sa_wrapper_name} textarea#hint").val()
- $.post final_url, {'assessment':assessment, 'hint':hint}, (response) ->
- if response.success
- $("#{sa_wrapper_name} p#save_message").append(response.message)
- $("#{sa_wrapper_name} input#save").remove()
-));
+ data = {'assessment' : @assessment.find(':selected').text(), 'hint' : @hint.val()}
+
+ $.postWithPrefix "#{@ajax_url}/save", data, (response) =>
+ if response.success
+ @rubric_wrapper.html(response.message)
+ else
+ @errors_area.html('There was an error saving your response.')
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index 193eb5a8d2..52881ff7ef 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -22,25 +22,22 @@ from .editing_module import EditingDescriptor
from .html_checker import check_html
from .stringify import stringify_children
from .x_module import XModule
-from .xml_module import XmlDescriptor, name_to_pathname
+from .xml_module import XmlDescriptor
from xmodule.modulestore import Location
-from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent
-
log = logging.getLogger("mitx.courseware")
-#Set the default number of max attempts. Should be 1 for production
-#Set higher for debugging/testing
+# Set the default number of max attempts. Should be 1 for production
+# Set higher for debugging/testing
# attempts specified in xml definition overrides this.
MAX_ATTEMPTS = 1
-#Set maximum available number of points. Should be set to 1 for now due to correctness handling,
+# Set maximum available number of points. Should be set to 1 for now due to correctness handling,
# which only allows for correct/incorrect.
-MAX_SCORE=1
+MAX_SCORE = 1
class SelfAssessmentModule(XModule):
- js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/display.coffee')]
- }
+ js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/display.coffee')]}
js_module_name = "SelfAssessment"
def get_html(self):
@@ -85,17 +82,21 @@ class SelfAssessmentModule(XModule):
log.debug('Instance state of self-assessment module {0}: {1}'.format(location.url(), instance_state))
# Pull out state, or initialize variables
- # lists of student answers, correctness responses ('incorrect'/'correct'), and suggested hints
+
+ # lists of student answers, correctness responses
+ # ('incorrect'/'correct'), and suggested hints
self.student_answers = instance_state.get('student_answers', [])
self.correctness = instance_state.get('correctness', [])
self.hints = instance_state.get('hints', [])
- # Used to keep track of a submitted answer for which we don't have a self-assessment and hint yet:
- # this means that the answers, correctness, hints always stay in sync, and have the same number of elements.
+ # Used to keep track of a submitted answer for which we don't have a
+ # self-assessment and hint yet: this means that the answers,
+ # correctness, hints always stay in sync, and have the same number of
+ # elements.
self.temp_answer = instance_state.get('temp_answer', '')
- # Used for progress / grading. Currently get credit just for completion (doesn't matter if you self-assessed
- # correct/incorrect).
+ # Used for progress / grading. Currently get credit just for
+ # completion (doesn't matter if you self-assessed correct/incorrect).
self.score = instance_state.get('score', 0)
self.top_score = instance_state.get('top_score', MAX_SCORE)
@@ -116,21 +117,15 @@ class SelfAssessmentModule(XModule):
#Determine if student has answered the question before. This is used to display
#a "previous answer" message to the student if they have.
- previous_answer=''
- if len(self.student_answers)>0:
- previous_answer=self.student_answers[len(self.student_answers)-1]
+ previous_answer = self.student_answers[-1] if self.student_answers else ''
#set context variables and render template
- self.context = {
+ context = {
'prompt' : self.prompt,
- 'rubric' : self.rubric,
- 'hint_prompt' : self.hint_prompt,
- 'previous_answer_given' : len(self.student_answers)>0,
'previous_answer' : previous_answer,
'ajax_url' : system.ajax_url,
- 'section_name' : 'sa-wrapper',
}
- self.html = self.system.render_template('self_assessment_prompt.html', self.context)
+ self.html = self.system.render_template('self_assessment_prompt.html', context)
def get_score(self):
return {'score': self.score}
@@ -153,7 +148,7 @@ class SelfAssessmentModule(XModule):
def handle_ajax(self, dispatch, get):
- '''
+ """
This is called by courseware.module_render, to handle an AJAX call.
"get" is request.POST.
@@ -161,11 +156,11 @@ class SelfAssessmentModule(XModule):
{ 'progress_changed' : True/False,
'progress' : 'none'/'in_progress'/'done',
}
- '''
+ """
handlers = {
- 'sa_show': self.show_rubric,
- 'sa_save': self.save_problem,
+ 'show': self.show_rubric,
+ 'save': self.save_problem,
}
if dispatch not in handlers:
@@ -182,21 +177,24 @@ class SelfAssessmentModule(XModule):
def show_rubric(self, get):
"""
- After the prompt is submitted, show the rubric
+ After the answer is submitted, show the rubric.
"""
- #Check to see if attempts are less than max
+ # Check to see if attempts are less than max
if(self.attempts < self.max_attempts):
# Dump to temp_answer to keep answer in sync with correctness and hint
self.temp_answer = get['student_answer']
- #Return success and return rubric html to ajax call
+ # Return success and return rubric html to ajax call
+ rubric_context = {'rubric' : self.rubric,
+ 'hint_prompt' : self.hint_prompt,}
+
return {
'success': True,
- 'rubric': self.system.render_template('self_assessment_rubric.html', self.context)
+ 'rubric': self.system.render_template('self_assessment_rubric.html', rubric_context)
}
else:
- #If too many attempts, prevent student from saving answer and seeing rubric.
- return{
+ # If too many attempts, prevent student from saving answer and seeing rubric.
+ return {
'success': False,
'message': 'Too many attempts.'
}
@@ -221,7 +219,7 @@ class SelfAssessmentModule(XModule):
#Create and store event info dict
#Currently points are assigned for completion, so set to 1 instead of depending on correctness.
- points=1
+ points = 1
event_info = dict()
event_info['state'] = {
'student_answers': self.student_answers,
@@ -315,4 +313,4 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
for child in ['rubric', 'prompt', 'submitmessage', 'hintprompt']:
add_child(child)
- return elt
\ No newline at end of file
+ return elt
diff --git a/lms/templates/self_assessment_prompt.html b/lms/templates/self_assessment_prompt.html
index 11c2940ea7..905f02596e 100644
--- a/lms/templates/self_assessment_prompt.html
+++ b/lms/templates/self_assessment_prompt.html
@@ -1,13 +1,14 @@
-
- ${ prompt }
- % if previous_answer_given:
- Previous answer: ${ previous_answer }
- % endif
-
-
-
-
-
-
+
+
+
+ ${prompt}
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
diff --git a/lms/templates/self_assessment_rubric.html b/lms/templates/self_assessment_rubric.html
index b157442b7a..0972998f5e 100644
--- a/lms/templates/self_assessment_rubric.html
+++ b/lms/templates/self_assessment_rubric.html
@@ -1,17 +1,18 @@
-
-
Rubric
- ${ rubric }
- Please assess your performance given the above rubric:
-
+
From f88cb685aa6bea200c659c2d82069d726a744e9b Mon Sep 17 00:00:00 2001
From: Victor Shnayder
Date: Fri, 9 Nov 2012 16:20:53 -0500
Subject: [PATCH 061/121] split out the assess and reflect states. Still need
matching js
---
.../xmodule/xmodule/self_assessment_module.py | 184 +++++++++++-------
1 file changed, 113 insertions(+), 71 deletions(-)
diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py
index 4151f127a7..3f84e24386 100644
--- a/common/lib/xmodule/xmodule/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/self_assessment_module.py
@@ -1,7 +1,7 @@
"""
A Self Assessment module that allows students to write open-ended responses,
submit, then see a rubric and rate themselves. Persists student supplied
-hints, answers, and correctness judgment (currently only correct/incorrect).
+hints, answers, and assessment judgment (currently only correct/incorrect).
Parses xml definition file--see below for exact format.
"""
@@ -33,7 +33,7 @@ log = logging.getLogger("mitx.courseware")
# attempts specified in xml definition overrides this.
MAX_ATTEMPTS = 1
-# Set maximum available number of points. Should be set to 1 for now due to correctness handling,
+# Set maximum available number of points. Should be set to 1 for now due to assessment handling,
# which only allows for correct/incorrect.
MAX_SCORE = 1
@@ -73,7 +73,7 @@ class SelfAssessmentModule(XModule):
"""
Definition file should have 4 blocks -- prompt, rubric, submitmessage, hintprompt,
and one optional attribute, attempts, which should be an integer that defaults to 1.
- If it's >1, the student will be able to re-submit after they see
+ If it's > 1, the student will be able to re-submit after they see
the rubric. Note: all the submissions are stored.
Sample file:
@@ -100,41 +100,28 @@ class SelfAssessmentModule(XModule):
else:
instance_state = {}
- log.debug('Instance state of self-assessment module {0}: {1}'.format(location.url(), instance_state))
-
- # Pull out state, or initialize variables
-
- # lists of student answers, correctness responses
- # ('incorrect'/'correct'), and suggested hints
self.student_answers = instance_state.get('student_answers', [])
- self.correctness = instance_state.get('correctness', [])
+
+ # assessment responses are 'incorrect'/'correct'
+ self.assessment = instance_state.get('assessment', [])
self.hints = instance_state.get('hints', [])
- # Used to keep track of a submitted answer for which we don't have a
- # self-assessment and hint yet: this means that the answers,
- # correctness, hints always stay in sync, and have the same number of
- # elements.
self.state = instance_state.get('state', 'initial')
# Used for progress / grading. Currently get credit just for
# completion (doesn't matter if you self-assessed correct/incorrect).
self.score = instance_state.get('score', 0)
- self.top_score = instance_state.get('top_score', MAX_SCORE)
+ self.max_score = instance_state.get('max_score', MAX_SCORE)
- #Get number of attempts student has used from instance state
self.attempts = instance_state.get('attempts', 0)
- #Try setting maxattempts, use default if not available in metadata
self.max_attempts = int(self.metadata.get('attempts', MAX_ATTEMPTS))
- #Extract prompt, submission message, hint prompt, and rubric from definition file
self.rubric = definition['rubric']
self.prompt = definition['prompt']
self.submit_message = definition['submitmessage']
self.hint_prompt = definition['hintprompt']
- #Determine if student has answered the question before. This is used to display
- #a "previous answer" message to the student if they have.
previous_answer = self.student_answers[-1] if self.student_answers else ''
#set context variables and render template
@@ -149,19 +136,24 @@ class SelfAssessmentModule(XModule):
self.html = self.system.render_template('self_assessment_prompt.html', context)
def get_score(self):
+ """
+ Returns dict with 'score' key
+ """
return {'score': self.score}
def max_score(self):
- return self.top_score
+ """
+ Return max_score
+ """
+ return self.max_score
def get_progress(self):
- ''' For now, just return score / max_score
'''
- score = self.score
- total = self.top_score
- if total > 0:
+ For now, just return score / max_score
+ '''
+ if self.max_score > 0:
try:
- return Progress(score, total)
+ return Progress(self.score, self.max_score)
except Exception as err:
log.exception("Got bad progress")
return None
@@ -180,8 +172,10 @@ class SelfAssessmentModule(XModule):
"""
handlers = {
- 'show': self.show_rubric,
- 'save': self.save_problem,
+ 'save_answer': self.save_answer,
+ 'save_assessment': self.save_assessment,
+ 'save_hint': self.save_hint,
+ 'reset': self.reset,
}
if dispatch not in handlers:
@@ -196,6 +190,15 @@ class SelfAssessmentModule(XModule):
})
return json.dumps(d, cls=ComplexEncoder)
+ def out_of_sync_error(self, get):
+ """
+ return dict out-of-sync error message, and also log.
+ """
+ log.warning("Assessment module state out sync. state: %r, get: %r",
+ self.state, get)
+ return {'success': False,
+ 'error': 'The problem state got out-of-sync'}
+
def get_rubric_html(self):
"""
Return the appropriate version of the rubric, based on the state.
@@ -245,77 +248,116 @@ class SelfAssessmentModule(XModule):
return """
{0}
""".format(self.message)
- def show_rubric(self, get):
+ def save_answer(self, get):
"""
After the answer is submitted, show the rubric.
"""
# Check to see if attempts are less than max
- if(self.attempts < self.max_attempts):
- # Dump to temp_answer to keep answer in sync with correctness and hint
- self.temp_answer = get['student_answer']
-
- return {
- 'success': True,
- 'rubric': self.get_rubric_html()
- }
- else:
- # If too many attempts, prevent student from saving answer and seeing rubric.
+ if self.attempts < self.max_attempts:
+ # If too many attempts, prevent student from saving answer and
+ # seeing rubric. In normal use, students shouldn't see this because
+ # they won't see the reset button once they're out of attempts.
return {
'success': False,
'message': 'Too many attempts.'
}
- def save_problem(self, get):
- '''
- Save the passed in answers.
- Returns a dict { 'success' : bool, ['error' : error-msg]},
- with the error key only present if success is False.
- '''
+ if self.state != self.INITIAL:
+ return self.out_of_sync_error(get)
- #Temp answer check is to keep hints, correctness, and answer in sync
- if self.temp_answer is not "":
- #Extract correctness and hint from ajax, and add temp answer to student answers
- self.hints.append(get['hint'])
- self.correctness.append(get['assessment'].lower())
- self.student_answers.append(self.temp_answer)
+ self.student_answers.append = get['student_answer']
+ self.state = self.ASSESSING
+ return {
+ 'success': True,
+ 'rubric': self.get_rubric_html()
+ }
+
+ def save_assessment(self, get):
+ """
+ Save the assessment.
+
+ Returns a dict { 'success' : bool, 'hint_html': hint_html 'error' : error-msg},
+ with 'error' only present if 'success' is False, and 'hint_html' only if success is true
+ """
+
+ if (self.state != self.ASSESSMENT or
+ len(self.student_answers) != len(self.assessment) + 1):
+ return self.out_of_sync_error(get)
+
+ self.assessment.append(get['assessment'].lower())
+ self.state = self.REQUEST_HINT
+
+ # TODO: return different hint based on assessment value...
+ return {'success': True, 'hint_html': self.get_hint_html()}
+
+ def save_hint(self, get):
+ '''
+ Save the hint.
+ Returns a dict { 'success' : bool,
+ 'message_html': message_html,
+ 'error' : error-msg},
+ with the error key only present if success is False and message_html
+ only if True.
+ '''
+ if self.state != self.REQUEST_HINT or len(self.assessment) != len(self.hints) + 1:
+ return self.out_of_sync_error(get)
+
+ self.hints.append(get['hint'].lower())
+ self.state = self.DONE
+
+ # Points are assigned for completion, so always set to 1
+ points = 1
# increment attempts
self.attempts = self.attempts + 1
- #Create and store event info dict
- #Currently points are assigned for completion, so set to 1 instead of depending on correctness.
- points = 1
- event_info = dict()
- event_info['state'] = {
- 'student_answers': self.student_answers,
- 'hints' : self.hints,
- 'correctness': self.correctness,
- 'score': points,
- 'done': self.done
- }
+ # To the tracking logs!
+ event_info = {
+ 'selfassessment_id' : self.location.url(),
+ 'state': {
+ 'student_answers': self.student_answers,
+ 'assessment': self.assessment,
+ 'hints' : self.hints,
+ 'score': points,
+ 'done': self.done,}}
+ self.system.track_function('save_hint', event_info)
- # TODO: figure out how to identify self assessment. May not want to confuse with problems.
- event_info['selfassessment_id'] = self.location.url()
+ return {'success': True,
+ 'message_html': self.get_message_html()}
- self.system.track_function('save_problem_succeed', event_info)
- #Return the submitmessage specified in xml defintion on success
- return {'success': True, 'message': self.submit_message}
+ def reset(self, get):
+ """
+ If resetting is allowed, reset the state.
+
+ Returns {'success': bool, 'error': msg}
+ (error only present if not success)
+ """
+ if self.state != DONE:
+ return self.out_of_sync_error(get)
+ if self.attempts < self.max_attempts:
+ return {
+ 'success': False,
+ 'error': 'Too many attempts.'
+ }
+ self.state = self.INITIAL
+ return {'success': True}
+
def get_instance_state(self):
"""
- Get the current correctness, points, and state
+ Get the current assessment, points, and state
"""
- #Assign points based on completion. May want to change to correctness-based down the road.
+ #Assign points based on completion. May want to change to assessment-based down the road.
points = 1
state = {
'student_answers': self.student_answers,
'temp_answer': self.temp_answer,
'hints' : self.hints,
- 'correctness': self.correctness,
+ 'assessment': self.assessment,
'score': points,
- 'top_score' : MAX_SCORE,
+ 'max_score' : MAX_SCORE,
'attempts' : self.attempts
}
return json.dumps(state)
From 76f0cf4a861da84796e448477c98496de7843b3c Mon Sep 17 00:00:00 2001
From: Victor Shnayder
Date: Tue, 30 Oct 2012 18:23:07 -0400
Subject: [PATCH 062/121] remove (almost) all references to askbot.
---
.gitignore | 2 -
.gitmodules | 3 -
.hgignore | 12 -
askbot | 1 -
common/djangoapps/student/models.py | 2 +
common/lib/xmodule/xmodule/x_module.py | 2 +-
create-dev-env.sh | 9 -
doc/overview.md | 2 +-
install.txt | 3 -
lms/askbot/skins/README | 71 -
lms/askbot/skins/__init__.py | 0
lms/askbot/skins/common/media/images/anon.png | Bin 687 -> 0 bytes
.../skins/common/media/images/bigbutton.png | Bin 263 -> 0 bytes
.../common/media/images/bigbuttonhover.png | Bin 236 -> 0 bytes
.../media/images/blue-up-arrow-h18px.png | Bin 593 -> 0 bytes
.../skins/common/media/images/box-arrow.gif | Bin 69 -> 0 bytes
.../common/media/images/bullet_green.gif | Bin 64 -> 0 bytes
.../skins/common/media/images/cc-88x31.png | Bin 5460 -> 0 bytes
.../skins/common/media/images/cc-by-sa.png | Bin 5083 -> 0 bytes
.../common/media/images/close-small-dark.png | Bin 226 -> 0 bytes
.../common/media/images/close-small-hover.png | Bin 337 -> 0 bytes
.../skins/common/media/images/close-small.png | Bin 293 -> 0 bytes
.../common/media/images/contributorsback.png | Bin 714 -> 0 bytes
lms/askbot/skins/common/media/images/dash.gif | Bin 44 -> 0 bytes
.../media/images/dialog-warning-off.png | Bin 419 -> 0 bytes
.../common/media/images/dialog-warning.png | Bin 603 -> 0 bytes
.../media/images/djangomade124x25_grey.gif | Bin 2035 -> 0 bytes
.../skins/common/media/images/dot-g.gif | Bin 61 -> 0 bytes
.../skins/common/media/images/dot-list.gif | Bin 56 -> 0 bytes
lms/askbot/skins/common/media/images/edit.png | Bin 758 -> 0 bytes
.../media/images/expander-arrow-hide.gif | Bin 126 -> 0 bytes
.../media/images/expander-arrow-show.gif | Bin 135 -> 0 bytes
.../skins/common/media/images/favicon.gif | Bin 78 -> 0 bytes
.../skins/common/media/images/favicon.ico | Bin 14846 -> 0 bytes
.../common/media/images/feed-icon-small.png | Bin 669 -> 0 bytes
.../skins/common/media/images/flags/ad.gif | Bin 371 -> 0 bytes
.../skins/common/media/images/flags/ae.gif | Bin 361 -> 0 bytes
.../skins/common/media/images/flags/af.gif | Bin 369 -> 0 bytes
.../skins/common/media/images/flags/ag.gif | Bin 361 -> 0 bytes
.../skins/common/media/images/flags/ai.gif | Bin 369 -> 0 bytes
.../skins/common/media/images/flags/al.gif | Bin 370 -> 0 bytes
.../skins/common/media/images/flags/am.gif | Bin 363 -> 0 bytes
.../skins/common/media/images/flags/an.gif | Bin 368 -> 0 bytes
.../skins/common/media/images/flags/ao.gif | Bin 244 -> 0 bytes
.../skins/common/media/images/flags/ar.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/as.gif | Bin 365 -> 0 bytes
.../skins/common/media/images/flags/at.gif | Bin 361 -> 0 bytes
.../skins/common/media/images/flags/au.gif | Bin 378 -> 0 bytes
.../skins/common/media/images/flags/aw.gif | Bin 365 -> 0 bytes
.../skins/common/media/images/flags/ax.gif | Bin 376 -> 0 bytes
.../skins/common/media/images/flags/az.gif | Bin 370 -> 0 bytes
.../skins/common/media/images/flags/ba.gif | Bin 363 -> 0 bytes
.../skins/common/media/images/flags/bb.gif | Bin 368 -> 0 bytes
.../skins/common/media/images/flags/bd.gif | Bin 361 -> 0 bytes
.../skins/common/media/images/flags/be.gif | Bin 359 -> 0 bytes
.../skins/common/media/images/flags/bf.gif | Bin 358 -> 0 bytes
.../skins/common/media/images/flags/bg.gif | Bin 360 -> 0 bytes
.../skins/common/media/images/flags/bh.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/bi.gif | Bin 374 -> 0 bytes
.../skins/common/media/images/flags/bj.gif | Bin 368 -> 0 bytes
.../skins/common/media/images/flags/bm.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/bn.gif | Bin 373 -> 0 bytes
.../skins/common/media/images/flags/bo.gif | Bin 359 -> 0 bytes
.../skins/common/media/images/flags/br.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/bs.gif | Bin 351 -> 0 bytes
.../skins/common/media/images/flags/bt.gif | Bin 377 -> 0 bytes
.../skins/common/media/images/flags/bv.gif | Bin 376 -> 0 bytes
.../skins/common/media/images/flags/bw.gif | Bin 364 -> 0 bytes
.../skins/common/media/images/flags/by.gif | Bin 361 -> 0 bytes
.../skins/common/media/images/flags/bz.gif | Bin 368 -> 0 bytes
.../skins/common/media/images/flags/ca.gif | Bin 376 -> 0 bytes
.../common/media/images/flags/catalonia.gif | Bin 238 -> 0 bytes
.../skins/common/media/images/flags/cc.gif | Bin 371 -> 0 bytes
.../skins/common/media/images/flags/cd.gif | Bin 243 -> 0 bytes
.../skins/common/media/images/flags/cf.gif | Bin 364 -> 0 bytes
.../skins/common/media/images/flags/cg.gif | Bin 359 -> 0 bytes
.../skins/common/media/images/flags/ch.gif | Bin 332 -> 0 bytes
.../skins/common/media/images/flags/ci.gif | Bin 368 -> 0 bytes
.../skins/common/media/images/flags/ck.gif | Bin 362 -> 0 bytes
.../skins/common/media/images/flags/cl.gif | Bin 364 -> 0 bytes
.../skins/common/media/images/flags/cm.gif | Bin 369 -> 0 bytes
.../skins/common/media/images/flags/cn.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/co.gif | Bin 353 -> 0 bytes
.../skins/common/media/images/flags/cr.gif | Bin 359 -> 0 bytes
.../skins/common/media/images/flags/cs.gif | Bin 364 -> 0 bytes
.../skins/common/media/images/flags/cu.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/cv.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/cx.gif | Bin 363 -> 0 bytes
.../skins/common/media/images/flags/cy.gif | Bin 365 -> 0 bytes
.../skins/common/media/images/flags/cz.gif | Bin 362 -> 0 bytes
.../skins/common/media/images/flags/de.gif | Bin 362 -> 0 bytes
.../skins/common/media/images/flags/dj.gif | Bin 369 -> 0 bytes
.../skins/common/media/images/flags/dk.gif | Bin 374 -> 0 bytes
.../skins/common/media/images/flags/dm.gif | Bin 368 -> 0 bytes
.../skins/common/media/images/flags/do.gif | Bin 362 -> 0 bytes
.../skins/common/media/images/flags/dz.gif | Bin 370 -> 0 bytes
.../skins/common/media/images/flags/ec.gif | Bin 362 -> 0 bytes
.../skins/common/media/images/flags/ee.gif | Bin 364 -> 0 bytes
.../skins/common/media/images/flags/eg.gif | Bin 363 -> 0 bytes
.../skins/common/media/images/flags/eh.gif | Bin 359 -> 0 bytes
.../common/media/images/flags/england.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/er.gif | Bin 361 -> 0 bytes
.../skins/common/media/images/flags/es.gif | Bin 360 -> 0 bytes
.../skins/common/media/images/flags/et.gif | Bin 364 -> 0 bytes
.../media/images/flags/europeanunion.gif | Bin 171 -> 0 bytes
.../skins/common/media/images/flags/fam.gif | Bin 370 -> 0 bytes
.../skins/common/media/images/flags/fi.gif | Bin 371 -> 0 bytes
.../skins/common/media/images/flags/fj.gif | Bin 370 -> 0 bytes
.../skins/common/media/images/flags/fk.gif | Bin 372 -> 0 bytes
.../skins/common/media/images/flags/fm.gif | Bin 377 -> 0 bytes
.../skins/common/media/images/flags/fo.gif | Bin 370 -> 0 bytes
.../skins/common/media/images/flags/fr.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/ga.gif | Bin 359 -> 0 bytes
.../skins/common/media/images/flags/gb.gif | Bin 260 -> 0 bytes
.../skins/common/media/images/flags/gd.gif | Bin 364 -> 0 bytes
.../skins/common/media/images/flags/ge.gif | Bin 379 -> 0 bytes
.../skins/common/media/images/flags/gf.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/gh.gif | Bin 358 -> 0 bytes
.../skins/common/media/images/flags/gi.gif | Bin 370 -> 0 bytes
.../skins/common/media/images/flags/gl.gif | Bin 368 -> 0 bytes
.../skins/common/media/images/flags/gm.gif | Bin 362 -> 0 bytes
.../skins/common/media/images/flags/gn.gif | Bin 363 -> 0 bytes
.../skins/common/media/images/flags/gp.gif | Bin 357 -> 0 bytes
.../skins/common/media/images/flags/gq.gif | Bin 361 -> 0 bytes
.../skins/common/media/images/flags/gr.gif | Bin 368 -> 0 bytes
.../skins/common/media/images/flags/gs.gif | Bin 363 -> 0 bytes
.../skins/common/media/images/flags/gt.gif | Bin 374 -> 0 bytes
.../skins/common/media/images/flags/gu.gif | Bin 370 -> 0 bytes
.../skins/common/media/images/flags/gw.gif | Bin 358 -> 0 bytes
.../skins/common/media/images/flags/gy.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/hk.gif | Bin 373 -> 0 bytes
.../skins/common/media/images/flags/hm.gif | Bin 378 -> 0 bytes
.../skins/common/media/images/flags/hn.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/hr.gif | Bin 364 -> 0 bytes
.../skins/common/media/images/flags/ht.gif | Bin 361 -> 0 bytes
.../skins/common/media/images/flags/hu.gif | Bin 357 -> 0 bytes
.../skins/common/media/images/flags/id.gif | Bin 362 -> 0 bytes
.../skins/common/media/images/flags/ie.gif | Bin 371 -> 0 bytes
.../skins/common/media/images/flags/il.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/in.gif | Bin 363 -> 0 bytes
.../skins/common/media/images/flags/io.gif | Bin 373 -> 0 bytes
.../skins/common/media/images/flags/iq.gif | Bin 361 -> 0 bytes
.../skins/common/media/images/flags/ir.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/is.gif | Bin 373 -> 0 bytes
.../skins/common/media/images/flags/it.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/jm.gif | Bin 365 -> 0 bytes
.../skins/common/media/images/flags/jo.gif | Bin 360 -> 0 bytes
.../skins/common/media/images/flags/jp.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/ke.gif | Bin 360 -> 0 bytes
.../skins/common/media/images/flags/kg.gif | Bin 373 -> 0 bytes
.../skins/common/media/images/flags/kh.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/ki.gif | Bin 371 -> 0 bytes
.../skins/common/media/images/flags/km.gif | Bin 358 -> 0 bytes
.../skins/common/media/images/flags/kn.gif | Bin 370 -> 0 bytes
.../skins/common/media/images/flags/kp.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/kr.gif | Bin 385 -> 0 bytes
.../skins/common/media/images/flags/kw.gif | Bin 362 -> 0 bytes
.../skins/common/media/images/flags/ky.gif | Bin 373 -> 0 bytes
.../skins/common/media/images/flags/kz.gif | Bin 374 -> 0 bytes
.../skins/common/media/images/flags/la.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/lb.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/lc.gif | Bin 259 -> 0 bytes
.../skins/common/media/images/flags/li.gif | Bin 359 -> 0 bytes
.../skins/common/media/images/flags/lk.gif | Bin 377 -> 0 bytes
.../skins/common/media/images/flags/lr.gif | Bin 360 -> 0 bytes
.../skins/common/media/images/flags/ls.gif | Bin 369 -> 0 bytes
.../skins/common/media/images/flags/lt.gif | Bin 362 -> 0 bytes
.../skins/common/media/images/flags/lu.gif | Bin 368 -> 0 bytes
.../skins/common/media/images/flags/lv.gif | Bin 363 -> 0 bytes
.../skins/common/media/images/flags/ly.gif | Bin 362 -> 0 bytes
.../skins/common/media/images/flags/ma.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/mc.gif | Bin 359 -> 0 bytes
.../skins/common/media/images/flags/md.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/me.gif | Bin 238 -> 0 bytes
.../skins/common/media/images/flags/mg.gif | Bin 372 -> 0 bytes
.../skins/common/media/images/flags/mh.gif | Bin 370 -> 0 bytes
.../skins/common/media/images/flags/mk.gif | Bin 382 -> 0 bytes
.../skins/common/media/images/flags/ml.gif | Bin 363 -> 0 bytes
.../skins/common/media/images/flags/mm.gif | Bin 365 -> 0 bytes
.../skins/common/media/images/flags/mn.gif | Bin 368 -> 0 bytes
.../skins/common/media/images/flags/mo.gif | Bin 378 -> 0 bytes
.../skins/common/media/images/flags/mp.gif | Bin 368 -> 0 bytes
.../skins/common/media/images/flags/mq.gif | Bin 379 -> 0 bytes
.../skins/common/media/images/flags/mr.gif | Bin 377 -> 0 bytes
.../skins/common/media/images/flags/ms.gif | Bin 371 -> 0 bytes
.../skins/common/media/images/flags/mt.gif | Bin 369 -> 0 bytes
.../skins/common/media/images/flags/mu.gif | Bin 358 -> 0 bytes
.../skins/common/media/images/flags/mv.gif | Bin 372 -> 0 bytes
.../skins/common/media/images/flags/mw.gif | Bin 364 -> 0 bytes
.../skins/common/media/images/flags/mx.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/my.gif | Bin 375 -> 0 bytes
.../skins/common/media/images/flags/mz.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/na.gif | Bin 371 -> 0 bytes
.../skins/common/media/images/flags/nc.gif | Bin 364 -> 0 bytes
.../skins/common/media/images/flags/ne.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/nf.gif | Bin 375 -> 0 bytes
.../skins/common/media/images/flags/ng.gif | Bin 371 -> 0 bytes
.../skins/common/media/images/flags/ni.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/nl.gif | Bin 360 -> 0 bytes
.../skins/common/media/images/flags/no.gif | Bin 376 -> 0 bytes
.../skins/common/media/images/flags/np.gif | Bin 302 -> 0 bytes
.../skins/common/media/images/flags/nr.gif | Bin 364 -> 0 bytes
.../skins/common/media/images/flags/nu.gif | Bin 369 -> 0 bytes
.../skins/common/media/images/flags/nz.gif | Bin 369 -> 0 bytes
.../skins/common/media/images/flags/om.gif | Bin 364 -> 0 bytes
.../skins/common/media/images/flags/pa.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/pe.gif | Bin 361 -> 0 bytes
.../skins/common/media/images/flags/pf.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/pg.gif | Bin 360 -> 0 bytes
.../skins/common/media/images/flags/ph.gif | Bin 361 -> 0 bytes
.../skins/common/media/images/flags/pk.gif | Bin 377 -> 0 bytes
.../skins/common/media/images/flags/pl.gif | Bin 360 -> 0 bytes
.../skins/common/media/images/flags/pm.gif | Bin 374 -> 0 bytes
.../skins/common/media/images/flags/pn.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/pr.gif | Bin 369 -> 0 bytes
.../skins/common/media/images/flags/ps.gif | Bin 358 -> 0 bytes
.../skins/common/media/images/flags/pt.gif | Bin 369 -> 0 bytes
.../skins/common/media/images/flags/pw.gif | Bin 374 -> 0 bytes
.../skins/common/media/images/flags/py.gif | Bin 363 -> 0 bytes
.../skins/common/media/images/flags/qa.gif | Bin 364 -> 0 bytes
.../skins/common/media/images/flags/re.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/ro.gif | Bin 363 -> 0 bytes
.../skins/common/media/images/flags/rs.gif | Bin 238 -> 0 bytes
.../skins/common/media/images/flags/ru.gif | Bin 361 -> 0 bytes
.../skins/common/media/images/flags/rw.gif | Bin 361 -> 0 bytes
.../skins/common/media/images/flags/sa.gif | Bin 370 -> 0 bytes
.../skins/common/media/images/flags/sb.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/sc.gif | Bin 357 -> 0 bytes
.../common/media/images/flags/scotland.gif | Bin 378 -> 0 bytes
.../skins/common/media/images/flags/sd.gif | Bin 355 -> 0 bytes
.../skins/common/media/images/flags/se.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/sg.gif | Bin 364 -> 0 bytes
.../skins/common/media/images/flags/sh.gif | Bin 371 -> 0 bytes
.../skins/common/media/images/flags/si.gif | Bin 362 -> 0 bytes
.../skins/common/media/images/flags/sj.gif | Bin 376 -> 0 bytes
.../skins/common/media/images/flags/sk.gif | Bin 361 -> 0 bytes
.../skins/common/media/images/flags/sl.gif | Bin 363 -> 0 bytes
.../skins/common/media/images/flags/sm.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/sn.gif | Bin 364 -> 0 bytes
.../skins/common/media/images/flags/so.gif | Bin 376 -> 0 bytes
.../skins/common/media/images/flags/sr.gif | Bin 361 -> 0 bytes
.../skins/common/media/images/flags/st.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/sv.gif | Bin 363 -> 0 bytes
.../skins/common/media/images/flags/sy.gif | Bin 361 -> 0 bytes
.../skins/common/media/images/flags/sz.gif | Bin 363 -> 0 bytes
.../skins/common/media/images/flags/tc.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/td.gif | Bin 368 -> 0 bytes
.../skins/common/media/images/flags/tf.gif | Bin 365 -> 0 bytes
.../skins/common/media/images/flags/tg.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/th.gif | Bin 360 -> 0 bytes
.../skins/common/media/images/flags/tj.gif | Bin 361 -> 0 bytes
.../skins/common/media/images/flags/tk.gif | Bin 372 -> 0 bytes
.../skins/common/media/images/flags/tl.gif | Bin 360 -> 0 bytes
.../skins/common/media/images/flags/tm.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/tn.gif | Bin 375 -> 0 bytes
.../skins/common/media/images/flags/to.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/tr.gif | Bin 371 -> 0 bytes
.../skins/common/media/images/flags/tt.gif | Bin 377 -> 0 bytes
.../skins/common/media/images/flags/tv.gif | Bin 361 -> 0 bytes
.../skins/common/media/images/flags/tw.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/tz.gif | Bin 366 -> 0 bytes
.../skins/common/media/images/flags/ua.gif | Bin 360 -> 0 bytes
.../skins/common/media/images/flags/ug.gif | Bin 359 -> 0 bytes
.../skins/common/media/images/flags/um.gif | Bin 371 -> 0 bytes
.../skins/common/media/images/flags/us.gif | Bin 367 -> 0 bytes
.../skins/common/media/images/flags/uy.gif | Bin 373 -> 0 bytes
.../skins/common/media/images/flags/uz.gif | Bin 364 -> 0 bytes
.../skins/common/media/images/flags/va.gif | Bin 369 -> 0 bytes
.../skins/common/media/images/flags/vc.gif | Bin 370 -> 0 bytes
.../skins/common/media/images/flags/ve.gif | Bin 364 -> 0 bytes
.../skins/common/media/images/flags/vg.gif | Bin 368 -> 0 bytes
.../skins/common/media/images/flags/vi.gif | Bin 376 -> 0 bytes
.../skins/common/media/images/flags/vn.gif | Bin 370 -> 0 bytes
.../skins/common/media/images/flags/vu.gif | Bin 365 -> 0 bytes
.../skins/common/media/images/flags/wales.gif | Bin 372 -> 0 bytes
.../skins/common/media/images/flags/wf.gif | Bin 377 -> 0 bytes
.../skins/common/media/images/flags/ws.gif | Bin 365 -> 0 bytes
.../skins/common/media/images/flags/ye.gif | Bin 356 -> 0 bytes
.../skins/common/media/images/flags/yt.gif | Bin 382 -> 0 bytes
.../skins/common/media/images/flags/za.gif | Bin 363 -> 0 bytes
.../skins/common/media/images/flags/zm.gif | Bin 358 -> 0 bytes
.../skins/common/media/images/flags/zw.gif | Bin 365 -> 0 bytes
.../skins/common/media/images/go-up-grey.png | Bin 563 -> 0 bytes
.../common/media/images/go-up-orange.png | Bin 586 -> 0 bytes
.../media/images/gray-up-arrow-h18px.png | Bin 383 -> 0 bytes
.../skins/common/media/images/grippie.png | Bin 162 -> 0 bytes
.../skins/common/media/images/indicator.gif | Bin 2545 -> 0 bytes
lms/askbot/skins/common/media/images/logo.gif | Bin 3792 -> 0 bytes
lms/askbot/skins/common/media/images/logo.png | Bin 5841 -> 0 bytes
.../skins/common/media/images/logo1.png | Bin 2752 -> 0 bytes
.../skins/common/media/images/logo2.png | Bin 2124 -> 0 bytes
.../media/images/mail-envelope-empty.png | Bin 547 -> 0 bytes
.../media/images/mail-envelope-full.png | Bin 482 -> 0 bytes
.../skins/common/media/images/medala.gif | Bin 801 -> 0 bytes
.../skins/common/media/images/medala_on.gif | Bin 957 -> 0 bytes
lms/askbot/skins/common/media/images/new.gif | Bin 635 -> 0 bytes
.../skins/common/media/images/nophoto.png | Bin 696 -> 0 bytes
.../skins/common/media/images/openid.gif | Bin 910 -> 0 bytes
.../skins/common/media/images/openid/aol.gif | Bin 2205 -> 0 bytes
.../common/media/images/openid/blogger.ico | Bin 3638 -> 0 bytes
.../common/media/images/openid/claimid.ico | Bin 3638 -> 0 bytes
.../common/media/images/openid/facebook.gif | Bin 2075 -> 0 bytes
.../common/media/images/openid/flickr.ico | Bin 1150 -> 0 bytes
.../common/media/images/openid/google.gif | Bin 1596 -> 0 bytes
.../media/images/openid/livejournal.ico | Bin 5222 -> 0 bytes
.../common/media/images/openid/myopenid.ico | Bin 2862 -> 0 bytes
.../media/images/openid/openid-inputicon.gif | Bin 237 -> 0 bytes
.../common/media/images/openid/openid.gif | Bin 740 -> 0 bytes
.../common/media/images/openid/technorati.ico | Bin 2294 -> 0 bytes
.../common/media/images/openid/twitter.png | Bin 3130 -> 0 bytes
.../common/media/images/openid/verisign.ico | Bin 4710 -> 0 bytes
.../common/media/images/openid/vidoop.ico | Bin 1406 -> 0 bytes
.../common/media/images/openid/wordpress.ico | Bin 1150 -> 0 bytes
.../common/media/images/openid/yahoo.gif | Bin 1510 -> 0 bytes
.../skins/common/media/images/print.png | Bin 1391 -> 0 bytes
.../skins/common/media/images/pw-login.gif | Bin 1818 -> 0 bytes
.../skins/common/media/images/quest-bg.gif | Bin 294 -> 0 bytes
.../skins/common/media/images/scopearrow.png | Bin 538 -> 0 bytes
.../skins/common/media/images/sprite.png | Bin 5325 -> 0 bytes
.../skins/common/media/images/sprites.png | Bin 12545 -> 0 bytes
.../media/images/sprites_source/sprites.svg | 732 --
.../media/images/summary-background.png | Bin 291 -> 0 bytes
.../skins/common/media/images/tag-left.png | Bin 290 -> 0 bytes
.../skins/common/media/images/tag-right.png | Bin 187 -> 0 bytes
.../common/media/images/vote-accepted-on.png | Bin 1124 -> 0 bytes
.../common/media/images/vote-accepted.png | Bin 1058 -> 0 bytes
.../media/images/vote-arrow-down-on.png | Bin 905 -> 0 bytes
.../common/media/images/vote-arrow-down.png | Bin 876 -> 0 bytes
.../common/media/images/vote-arrow-up-on.png | Bin 906 -> 0 bytes
.../common/media/images/vote-arrow-up.png | Bin 843 -> 0 bytes
.../common/media/images/vote-favorite-off.png | Bin 930 -> 0 bytes
.../common/media/images/vote-favorite-on.png | Bin 1023 -> 0 bytes
lms/askbot/skins/common/media/images/wiki.png | Bin 5178 -> 0 bytes
.../common/media/jquery-openid/images/aol.gif | Bin 1872 -> 0 bytes
.../media/jquery-openid/images/blogger-1.png | Bin 432 -> 0 bytes
.../media/jquery-openid/images/blogger.ico | Bin 3638 -> 0 bytes
.../media/jquery-openid/images/claimid-0.png | Bin 629 -> 0 bytes
.../media/jquery-openid/images/claimid.ico | Bin 3638 -> 0 bytes
.../media/jquery-openid/images/facebook.gif | Bin 1737 -> 0 bytes
.../media/jquery-openid/images/flickr.ico | Bin 1150 -> 0 bytes
.../media/jquery-openid/images/flickr.png | Bin 426 -> 0 bytes
.../media/jquery-openid/images/google.gif | Bin 1528 -> 0 bytes
.../media/jquery-openid/images/identica.png | Bin 6601 -> 0 bytes
.../media/jquery-openid/images/linkedin.gif | Bin 1530 -> 0 bytes
.../jquery-openid/images/livejournal-1.png | Bin 713 -> 0 bytes
.../jquery-openid/images/livejournal.ico | Bin 5222 -> 0 bytes
.../media/jquery-openid/images/myopenid-2.png | Bin 511 -> 0 bytes
.../media/jquery-openid/images/myopenid.ico | Bin 2862 -> 0 bytes
.../jquery-openid/images/openid-inputicon.gif | Bin 237 -> 0 bytes
.../media/jquery-openid/images/openid.gif | Bin 1473 -> 0 bytes
.../media/jquery-openid/images/openidico.png | Bin 654 -> 0 bytes
.../jquery-openid/images/openidico16.png | Bin 554 -> 0 bytes
.../jquery-openid/images/technorati-1.png | Bin 606 -> 0 bytes
.../media/jquery-openid/images/technorati.ico | Bin 2294 -> 0 bytes
.../media/jquery-openid/images/twitter.gif | Bin 1913 -> 0 bytes
.../media/jquery-openid/images/verisign-2.png | Bin 859 -> 0 bytes
.../media/jquery-openid/images/verisign.ico | Bin 4710 -> 0 bytes
.../media/jquery-openid/images/vidoop.ico | Bin 1406 -> 0 bytes
.../media/jquery-openid/images/vidoop.png | Bin 499 -> 0 bytes
.../media/jquery-openid/images/wordpress.ico | Bin 1150 -> 0 bytes
.../media/jquery-openid/images/wordpress.png | Bin 566 -> 0 bytes
.../media/jquery-openid/images/yahoo.gif | Bin 1607 -> 0 bytes
.../media/jquery-openid/jquery.openid.js | 440 --
.../common/media/jquery-openid/openid.css | 39 -
.../skins/common/media/js/autocompleter.js | 766 --
lms/askbot/skins/common/media/js/compress.bat | 5 -
lms/askbot/skins/common/media/js/editor.js | 75 -
.../skins/common/media/js/excanvas.min.js | 1 -
.../skins/common/media/js/flot-build.bat | 3 -
.../skins/common/media/js/jquery-1.4.3.js | 6883 -----------------
.../common/media/js/jquery-fieldselection.js | 83 -
.../media/js/jquery-fieldselection.min.js | 1 -
.../common/media/js/jquery.ajaxfileupload.js | 195 -
.../common/media/js/jquery.animate-colors.js | 105 -
.../skins/common/media/js/jquery.flot.js | 2119 -----
.../skins/common/media/js/jquery.flot.min.js | 1 -
.../skins/common/media/js/jquery.form.js | 654 --
.../skins/common/media/js/jquery.history.js | 1 -
.../skins/common/media/js/jquery.i18n.js | 133 -
.../skins/common/media/js/jquery.openid.js | 176 -
.../skins/common/media/js/jquery.validate.js | 1146 ---
.../common/media/js/jquery.validate.min.js | 16 -
.../common/media/js/jquery.validate.pack.js | 15 -
lms/askbot/skins/common/media/js/less.min.js | 16 -
.../skins/common/media/js/live_search.js | 276 -
.../common/media/js/live_search_new_thread.js | 82 -
.../skins/common/media/js/modernizr.custom.js | 4 -
.../skins/common/media/js/output-words.html | 49 -
.../skins/common/media/js/output-words.js | 97 -
lms/askbot/skins/common/media/js/post.js | 1952 -----
lms/askbot/skins/common/media/js/se_hilite.js | 1 -
.../skins/common/media/js/se_hilite_src.js | 273 -
.../skins/common/media/js/tag_selector.js | 375 -
lms/askbot/skins/common/media/js/user.js | 215 -
lms/askbot/skins/common/media/js/utils.js | 493 --
.../wmd/images/editor-toolbar-background.png | Bin 282 -> 0 bytes
.../media/js/wmd/images/wmd-buttons.png | Bin 11480 -> 0 bytes
.../skins/common/media/js/wmd/showdown-min.js | 1 -
.../skins/common/media/js/wmd/showdown.js | 1332 ----
.../skins/common/media/js/wmd/wmd-min.js | 1 -
.../skins/common/media/js/wmd/wmd-test.html | 158 -
lms/askbot/skins/common/media/js/wmd/wmd.css | 131 -
lms/askbot/skins/common/media/js/wmd/wmd.js | 2438 ------
lms/askbot/skins/common/media/style/auth.css | 48 -
.../media/style/jquery.autocomplete.css | 37 -
.../skins/common/media/style/lib_style.less | 38 -
.../skins/common/media/style/openid.css | 45 -
.../skins/common/media/style/prettify.css | 27 -
lms/askbot/skins/common/media/style/style.css | 2616 -------
.../authopenid/authopenid_macros.html | 58 -
.../templates/authopenid/changeemail.html | 80 -
.../common/templates/authopenid/complete.html | 84 -
.../templates/authopenid/confirm_email.txt | 12 -
.../templates/authopenid/email_validation.txt | 14 -
.../common/templates/authopenid/logout.html | 31 -
.../authopenid/providers_javascript.html | 55 -
.../common/templates/authopenid/signin.html | 245 -
.../authopenid/signup_with_password.html | 58 -
.../skins/common/templates/avatar/add.html | 15 -
.../skins/common/templates/avatar/change.html | 24 -
.../templates/avatar/confirm_delete.html | 15 -
.../skins/common/templates/debug_header.html | 27 -
.../common/templates/one_column_body.html | 10 -
.../question/answer_author_info.html | 6 -
.../templates/question/answer_comments.html | 10 -
.../templates/question/answer_controls.html | 42 -
.../question/answer_vote_buttons.html | 22 -
.../question/closed_question_info.html | 5 -
.../question/question_author_info.html | 6 -
.../templates/question/question_comments.html | 10 -
.../templates/question/question_controls.html | 44 -
.../templates/question/question_tags.html | 7 -
.../question/question_vote_buttons.html | 1 -
.../templates/question/share_buttons.html | 5 -
.../common/templates/two_column_body.html | 14 -
.../common/templates/widgets/edit_post.html | 63 -
.../templates/widgets/related_tags.html | 21 -
.../common/templates/widgets/search_bar.html | 48 -
.../templates/widgets/tag_selector.html | 47 -
lms/askbot/skins/loaders.py | 132 -
lms/askbot/skins/mitx/media/images/accept.png | Bin 727 -> 0 bytes
lms/askbot/skins/mitx/media/images/anon.png | Bin 687 -> 0 bytes
.../mitx/media/images/answers-background.png | Bin 235 -> 0 bytes
.../media/images/background-user-info.png | Bin 361 -> 0 bytes
.../skins/mitx/media/images/bigbutton.png | Bin 263 -> 0 bytes
.../mitx/media/images/bigbuttonhover.png | Bin 236 -> 0 bytes
.../mitx/media/images/blue-up-arrow-h18px.png | Bin 593 -> 0 bytes
.../skins/mitx/media/images/box-arrow.gif | Bin 69 -> 0 bytes
.../skins/mitx/media/images/bullet_green.gif | Bin 64 -> 0 bytes
.../skins/mitx/media/images/cc-88x31.png | Bin 5460 -> 0 bytes
.../skins/mitx/media/images/cc-by-sa.png | Bin 5083 -> 0 bytes
.../mitx/media/images/close-small-dark.png | Bin 879 -> 0 bytes
.../mitx/media/images/close-small-hover.png | Bin 337 -> 0 bytes
.../skins/mitx/media/images/close-small.png | Bin 293 -> 0 bytes
lms/askbot/skins/mitx/media/images/close.png | Bin 469 -> 0 bytes
.../mitx/media/images/comment-background.png | Bin 250 -> 0 bytes
.../skins/mitx/media/images/comment.png | Bin 606 -> 0 bytes
.../mitx/media/images/contributorsback.png | Bin 714 -> 0 bytes
lms/askbot/skins/mitx/media/images/dash.gif | Bin 44 -> 0 bytes
lms/askbot/skins/mitx/media/images/delete.png | Bin 434 -> 0 bytes
.../mitx/media/images/dialog-warning-off.png | Bin 419 -> 0 bytes
.../mitx/media/images/dialog-warning.png | Bin 603 -> 0 bytes
.../media/images/djangomade124x25_grey.gif | Bin 2035 -> 0 bytes
lms/askbot/skins/mitx/media/images/dot-g.gif | Bin 61 -> 0 bytes
.../skins/mitx/media/images/dot-list.gif | Bin 56 -> 0 bytes
lms/askbot/skins/mitx/media/images/edit.png | Bin 758 -> 0 bytes
lms/askbot/skins/mitx/media/images/edit2.png | Bin 498 -> 0 bytes
.../skins/mitx/media/images/email-sharing.png | Bin 1095 -> 0 bytes
.../mitx/media/images/expander-arrow-hide.gif | Bin 126 -> 0 bytes
.../mitx/media/images/expander-arrow-show.gif | Bin 135 -> 0 bytes
.../mitx/media/images/facebook-sharing.png | Bin 1085 -> 0 bytes
.../skins/mitx/media/images/favicon.gif | Bin 78 -> 0 bytes
.../skins/mitx/media/images/favicon.ico | Bin 14846 -> 0 bytes
.../mitx/media/images/feed-icon-small.png | Bin 669 -> 0 bytes
lms/askbot/skins/mitx/media/images/flag.png | Bin 515 -> 0 bytes
.../skins/mitx/media/images/go-up-grey.png | Bin 563 -> 0 bytes
.../skins/mitx/media/images/go-up-orange.png | Bin 586 -> 0 bytes
.../mitx/media/images/google-plus-sharing.png | Bin 1161 -> 0 bytes
.../mitx/media/images/gray-up-arrow-h18px.png | Bin 383 -> 0 bytes
.../skins/mitx/media/images/grippie.png | Bin 162 -> 0 bytes
.../skins/mitx/media/images/indicator.gif | Bin 2545 -> 0 bytes
lms/askbot/skins/mitx/media/images/link.png | Bin 601 -> 0 bytes
lms/askbot/skins/mitx/media/images/logo.gif | Bin 2249 -> 0 bytes
lms/askbot/skins/mitx/media/images/logo.png | Bin 5841 -> 0 bytes
lms/askbot/skins/mitx/media/images/logo1.png | Bin 2752 -> 0 bytes
lms/askbot/skins/mitx/media/images/logo2.png | Bin 2124 -> 0 bytes
.../mitx/media/images/lrg/email-sharing.png | Bin 8874 -> 0 bytes
.../media/images/lrg/facebook-sharing.png | Bin 8433 -> 0 bytes
.../skins/mitx/media/images/lrg/facebook.png | Bin 205 -> 0 bytes
.../media/images/lrg/google-plus-sharing.png | Bin 9367 -> 0 bytes
.../skins/mitx/media/images/lrg/linkedin.png | Bin 229 -> 0 bytes
.../mitx/media/images/lrg/twitter-sharing.png | Bin 8867 -> 0 bytes
.../skins/mitx/media/images/lrg/twitter.png | Bin 235 -> 0 bytes
.../mitx/media/images/lrg/youtube-sharing.png | Bin 14387 -> 0 bytes
.../mitx/media/images/mail-envelope-empty.png | Bin 547 -> 0 bytes
.../mitx/media/images/mail-envelope-full.png | Bin 482 -> 0 bytes
lms/askbot/skins/mitx/media/images/medala.gif | Bin 801 -> 0 bytes
.../skins/mitx/media/images/medala_on.gif | Bin 957 -> 0 bytes
.../skins/mitx/media/images/medium-button.png | Bin 217 -> 0 bytes
lms/askbot/skins/mitx/media/images/new.gif | Bin 635 -> 0 bytes
.../skins/mitx/media/images/nophoto.png | Bin 696 -> 0 bytes
.../skins/mitx/media/images/notification.png | Bin 217 -> 0 bytes
lms/askbot/skins/mitx/media/images/openid.gif | Bin 910 -> 0 bytes
lms/askbot/skins/mitx/media/images/print.png | Bin 1391 -> 0 bytes
.../skins/mitx/media/images/pw-login.gif | Bin 1818 -> 0 bytes
.../skins/mitx/media/images/quest-bg.gif | Bin 294 -> 0 bytes
lms/askbot/skins/mitx/media/images/retag.png | Bin 474 -> 0 bytes
.../skins/mitx/media/images/scopearrow.png | Bin 538 -> 0 bytes
.../mitx/media/images/small-button-blue.png | Bin 202 -> 0 bytes
.../mitx/media/images/small-button-cancel.png | Bin 211 -> 0 bytes
.../media/images/social/email-sharing.png | Bin 1095 -> 0 bytes
.../media/images/social/facebook-sharing.png | Bin 1085 -> 0 bytes
.../images/social/google-plus-sharing.png | Bin 1161 -> 0 bytes
.../media/images/social/twitter-sharing.png | Bin 1065 -> 0 bytes
.../media/images/social/youtube-sharing.png | Bin 1396 -> 0 bytes
.../skins/mitx/media/images/socialsprite.png | Bin 3030 -> 0 bytes
lms/askbot/skins/mitx/media/images/sprite.png | Bin 5325 -> 0 bytes
.../skins/mitx/media/images/sprites.png | Bin 12705 -> 0 bytes
.../media/images/sprites_source/graphics.svg | 1291 ----
.../media/images/sprites_source/sprites.svg | 507 --
.../mitx/media/images/summary-background.png | Bin 233 -> 0 bytes
.../skins/mitx/media/images/tag-left.png | Bin 488 -> 0 bytes
.../skins/mitx/media/images/tag-right.png | Bin 365 -> 0 bytes
lms/askbot/skins/mitx/media/images/tips.png | Bin 716 -> 0 bytes
.../mitx/media/images/twitter-sharing.png | Bin 1065 -> 0 bytes
.../mitx/media/images/view-background.png | Bin 265 -> 0 bytes
.../mitx/media/images/vote-accepted-on.png | Bin 1124 -> 0 bytes
.../skins/mitx/media/images/vote-accepted.png | Bin 1058 -> 0 bytes
.../mitx/media/images/vote-arrow-down-new.png | Bin 1458 -> 0 bytes
.../media/images/vote-arrow-down-on-new.png | Bin 980 -> 0 bytes
.../mitx/media/images/vote-arrow-down-on.png | Bin 905 -> 0 bytes
.../mitx/media/images/vote-arrow-down.png | Bin 876 -> 0 bytes
.../mitx/media/images/vote-arrow-up-new.png | Bin 979 -> 0 bytes
.../media/images/vote-arrow-up-on-new.png | Bin 1029 -> 0 bytes
.../mitx/media/images/vote-arrow-up-on.png | Bin 906 -> 0 bytes
.../skins/mitx/media/images/vote-arrow-up.png | Bin 843 -> 0 bytes
.../mitx/media/images/vote-background.png | Bin 225 -> 0 bytes
.../mitx/media/images/vote-favorite-off.png | Bin 930 -> 0 bytes
.../mitx/media/images/vote-favorite-on.png | Bin 1023 -> 0 bytes
lms/askbot/skins/mitx/media/images/wiki.png | Bin 5178 -> 0 bytes
.../mitx/media/images/youtube-sharing.png | Bin 1396 -> 0 bytes
lms/askbot/skins/mitx/media/style/auth.css | 48 -
lms/askbot/skins/mitx/media/style/extra.css | 227 -
.../mitx/media/style/jquery.autocomplete.css | 40 -
.../skins/mitx/media/style/lib_style.less | 65 -
lms/askbot/skins/mitx/media/style/openid.css | 45 -
.../skins/mitx/media/style/prettify.css | 27 -
lms/askbot/skins/mitx/media/style/style.css | 3165 --------
lms/askbot/skins/mitx/media/style/style.less | 3351 --------
lms/askbot/skins/mitx/templates/404.html | 5 -
.../skins/mitx/templates/404.jinja.html | 44 -
lms/askbot/skins/mitx/templates/500.html | 5 -
.../skins/mitx/templates/500.jinja.html | 25 -
.../skins/mitx/templates/answer_edit.html | 84 -
lms/askbot/skins/mitx/templates/ask.html | 67 -
lms/askbot/skins/mitx/templates/badge.html | 40 -
lms/askbot/skins/mitx/templates/badges.html | 66 -
lms/askbot/skins/mitx/templates/base.html | 47 -
lms/askbot/skins/mitx/templates/close.html | 28 -
.../templates/course_navigation.jinja.html | 16 -
.../skins/mitx/templates/faq_static.html | 93 -
lms/askbot/skins/mitx/templates/feedback.html | 68 -
.../skins/mitx/templates/feedback_email.txt | 13 -
lms/askbot/skins/mitx/templates/help.html | 33 -
.../skins/mitx/templates/import_data.html | 31 -
.../mitx/templates/instant_notification.html | 42 -
lms/askbot/skins/mitx/templates/macros.html | 625 --
.../skins/mitx/templates/main_page.html | 28 -
.../mitx/templates/main_page/content.html | 3 -
.../mitx/templates/main_page/headline.html | 42 -
.../mitx/templates/main_page/javascript.html | 32 -
.../templates/main_page/nothing_found.html | 30 -
.../mitx/templates/main_page/paginator.html | 7 -
.../templates/main_page/questions_loop.html | 14 -
.../templates/main_page/scope_filters.html | 16 -
.../mitx/templates/main_page/sidebar.html | 34 -
.../mitx/templates/main_page/tab_bar.html | 119 -
.../mitx/templates/meta/bottom_scripts.html | 107 -
.../mitx/templates/meta/editor_data.html | 13 -
.../templates/meta/html_head_javascript.html | 11 -
.../mitx/templates/meta/html_head_meta.html | 8 -
.../templates/meta/html_head_stylesheets.html | 3 -
.../templates/meta/mandatory_tags_js.html | 25 -
.../mitx/templates/navigation.jinja.html | 27 -
lms/askbot/skins/mitx/templates/question.html | 23 -
.../mitx/templates/question/answer_card.html | 41 -
.../templates/question/answer_tab_bar.html | 29 -
.../mitx/templates/question/content.html | 42 -
.../mitx/templates/question/javascript.html | 91 -
.../templates/question/new_answer_form.html | 59 -
.../templates/question/question_card.html | 47 -
.../question/sharing_prompt_phrase.html | 13 -
.../mitx/templates/question/sidebar.html | 63 -
.../question/subscribe_by_email_prompt.html | 23 -
.../skins/mitx/templates/question_edit.html | 96 -
.../skins/mitx/templates/question_retag.html | 68 -
.../skins/mitx/templates/question_widget.html | 21 -
lms/askbot/skins/mitx/templates/reopen.html | 38 -
.../skins/mitx/templates/revisions.html | 102 -
.../skins/mitx/templates/static_page.html | 10 -
.../mitx/templates/subscribe_for_tags.html | 19 -
lms/askbot/skins/mitx/templates/tags.html | 75 -
.../mitx/templates/user_profile/user.html | 30 -
.../templates/user_profile/user_edit.html | 120 -
.../user_email_subscriptions.html | 27 -
.../user_profile/user_favorites.html | 9 -
.../templates/user_profile/user_inbox.html | 131 -
.../templates/user_profile/user_info.html | 90 -
.../templates/user_profile/user_moderate.html | 93 -
.../templates/user_profile/user_network.html | 25 -
.../templates/user_profile/user_recent.html | 40 -
.../user_profile/user_reputation.html | 41 -
.../templates/user_profile/user_stats.html | 155 -
.../templates/user_profile/user_tabs.html | 57 -
.../templates/user_profile/user_votes.html | 30 -
.../user_profile/users_questions.html | 9 -
lms/askbot/skins/mitx/templates/users.html | 60 -
.../templates/widgets/answer_edit_tips.html | 64 -
.../mitx/templates/widgets/ask_button.html | 6 -
.../mitx/templates/widgets/ask_form.html | 49 -
.../mitx/templates/widgets/contributors.html | 10 -
.../skins/mitx/templates/widgets/footer.html | 38 -
.../skins/mitx/templates/widgets/header.html | 4 -
.../skins/mitx/templates/widgets/logo.html | 5 -
.../mitx/templates/widgets/meta_nav.html | 15 -
.../templates/widgets/question_edit_tips.html | 69 -
.../templates/widgets/question_summary.html | 63 -
.../mitx/templates/widgets/scope_nav.html | 25 -
.../templates/widgets/secondary_header.html | 21 -
.../templates/widgets/system_messages.html | 8 -
.../mitx/templates/widgets/user_list.html | 22 -
.../user_long_score_and_badge_summary.html | 21 -
.../templates/widgets/user_navigation.html | 17 -
.../widgets/user_score_and_badge_summary.html | 19 -
lms/askbot/skins/utils.py | 195 -
lms/djangoapps/courseware/tabs.py | 3 -
lms/envs/askbotsettings.py | 293 -
lms/envs/aws.py | 7 +-
lms/envs/common.py | 61 +-
lms/envs/dev.py | 13 +-
lms/envs/dev_ike.py | 1 -
lms/envs/devgroups/courses.py | 4 -
lms/envs/devgroups/h_cs50.py | 1 -
lms/envs/devgroups/m_6002.py | 1 -
lms/envs/devplus.py | 2 +-
lms/envs/edx4edx_aws.py | 1 -
lms/envs/static.py | 12 +-
lms/envs/test.py | 16 +-
lms/envs/test_ike.py | 14 +-
lms/static/sass/course.scss | 1 +
lms/urls.py | 21 +-
651 files changed, 17 insertions(+), 39091 deletions(-)
delete mode 100644 .hgignore
delete mode 160000 askbot
delete mode 100644 lms/askbot/skins/README
delete mode 100644 lms/askbot/skins/__init__.py
delete mode 100644 lms/askbot/skins/common/media/images/anon.png
delete mode 100644 lms/askbot/skins/common/media/images/bigbutton.png
delete mode 100644 lms/askbot/skins/common/media/images/bigbuttonhover.png
delete mode 100755 lms/askbot/skins/common/media/images/blue-up-arrow-h18px.png
delete mode 100755 lms/askbot/skins/common/media/images/box-arrow.gif
delete mode 100755 lms/askbot/skins/common/media/images/bullet_green.gif
delete mode 100755 lms/askbot/skins/common/media/images/cc-88x31.png
delete mode 100644 lms/askbot/skins/common/media/images/cc-by-sa.png
delete mode 100755 lms/askbot/skins/common/media/images/close-small-dark.png
delete mode 100755 lms/askbot/skins/common/media/images/close-small-hover.png
delete mode 100755 lms/askbot/skins/common/media/images/close-small.png
delete mode 100644 lms/askbot/skins/common/media/images/contributorsback.png
delete mode 100755 lms/askbot/skins/common/media/images/dash.gif
delete mode 100644 lms/askbot/skins/common/media/images/dialog-warning-off.png
delete mode 100644 lms/askbot/skins/common/media/images/dialog-warning.png
delete mode 100755 lms/askbot/skins/common/media/images/djangomade124x25_grey.gif
delete mode 100755 lms/askbot/skins/common/media/images/dot-g.gif
delete mode 100755 lms/askbot/skins/common/media/images/dot-list.gif
delete mode 100755 lms/askbot/skins/common/media/images/edit.png
delete mode 100755 lms/askbot/skins/common/media/images/expander-arrow-hide.gif
delete mode 100755 lms/askbot/skins/common/media/images/expander-arrow-show.gif
delete mode 100644 lms/askbot/skins/common/media/images/favicon.gif
delete mode 100644 lms/askbot/skins/common/media/images/favicon.ico
delete mode 100644 lms/askbot/skins/common/media/images/feed-icon-small.png
delete mode 100755 lms/askbot/skins/common/media/images/flags/ad.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ae.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/af.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ag.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ai.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/al.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/am.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/an.gif
delete mode 100644 lms/askbot/skins/common/media/images/flags/ao.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ar.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/as.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/at.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/au.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/aw.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ax.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/az.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ba.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/bb.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/bd.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/be.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/bf.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/bg.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/bh.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/bi.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/bj.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/bm.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/bn.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/bo.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/br.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/bs.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/bt.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/bv.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/bw.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/by.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/bz.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ca.gif
delete mode 100644 lms/askbot/skins/common/media/images/flags/catalonia.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/cc.gif
delete mode 100644 lms/askbot/skins/common/media/images/flags/cd.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/cf.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/cg.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ch.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ci.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ck.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/cl.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/cm.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/cn.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/co.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/cr.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/cs.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/cu.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/cv.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/cx.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/cy.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/cz.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/de.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/dj.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/dk.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/dm.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/do.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/dz.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ec.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ee.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/eg.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/eh.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/england.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/er.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/es.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/et.gif
delete mode 100644 lms/askbot/skins/common/media/images/flags/europeanunion.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/fam.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/fi.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/fj.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/fk.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/fm.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/fo.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/fr.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ga.gif
delete mode 100644 lms/askbot/skins/common/media/images/flags/gb.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/gd.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ge.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/gf.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/gh.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/gi.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/gl.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/gm.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/gn.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/gp.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/gq.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/gr.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/gs.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/gt.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/gu.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/gw.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/gy.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/hk.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/hm.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/hn.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/hr.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ht.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/hu.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/id.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ie.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/il.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/in.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/io.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/iq.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ir.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/is.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/it.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/jm.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/jo.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/jp.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ke.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/kg.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/kh.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ki.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/km.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/kn.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/kp.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/kr.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/kw.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ky.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/kz.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/la.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/lb.gif
delete mode 100644 lms/askbot/skins/common/media/images/flags/lc.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/li.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/lk.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/lr.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ls.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/lt.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/lu.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/lv.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ly.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ma.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/mc.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/md.gif
delete mode 100644 lms/askbot/skins/common/media/images/flags/me.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/mg.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/mh.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/mk.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ml.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/mm.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/mn.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/mo.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/mp.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/mq.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/mr.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ms.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/mt.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/mu.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/mv.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/mw.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/mx.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/my.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/mz.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/na.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/nc.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ne.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/nf.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ng.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ni.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/nl.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/no.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/np.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/nr.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/nu.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/nz.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/om.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/pa.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/pe.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/pf.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/pg.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ph.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/pk.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/pl.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/pm.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/pn.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/pr.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ps.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/pt.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/pw.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/py.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/qa.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/re.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ro.gif
delete mode 100644 lms/askbot/skins/common/media/images/flags/rs.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ru.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/rw.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/sa.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/sb.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/sc.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/scotland.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/sd.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/se.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/sg.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/sh.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/si.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/sj.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/sk.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/sl.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/sm.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/sn.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/so.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/sr.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/st.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/sv.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/sy.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/sz.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/tc.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/td.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/tf.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/tg.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/th.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/tj.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/tk.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/tl.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/tm.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/tn.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/to.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/tr.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/tt.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/tv.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/tw.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/tz.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ua.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ug.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/um.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/us.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/uy.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/uz.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/va.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/vc.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ve.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/vg.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/vi.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/vn.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/vu.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/wales.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/wf.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ws.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/ye.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/yt.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/za.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/zm.gif
delete mode 100755 lms/askbot/skins/common/media/images/flags/zw.gif
delete mode 100644 lms/askbot/skins/common/media/images/go-up-grey.png
delete mode 100644 lms/askbot/skins/common/media/images/go-up-orange.png
delete mode 100755 lms/askbot/skins/common/media/images/gray-up-arrow-h18px.png
delete mode 100755 lms/askbot/skins/common/media/images/grippie.png
delete mode 100755 lms/askbot/skins/common/media/images/indicator.gif
delete mode 100644 lms/askbot/skins/common/media/images/logo.gif
delete mode 100644 lms/askbot/skins/common/media/images/logo.png
delete mode 100755 lms/askbot/skins/common/media/images/logo1.png
delete mode 100755 lms/askbot/skins/common/media/images/logo2.png
delete mode 100644 lms/askbot/skins/common/media/images/mail-envelope-empty.png
delete mode 100644 lms/askbot/skins/common/media/images/mail-envelope-full.png
delete mode 100755 lms/askbot/skins/common/media/images/medala.gif
delete mode 100755 lms/askbot/skins/common/media/images/medala_on.gif
delete mode 100755 lms/askbot/skins/common/media/images/new.gif
delete mode 100755 lms/askbot/skins/common/media/images/nophoto.png
delete mode 100755 lms/askbot/skins/common/media/images/openid.gif
delete mode 100755 lms/askbot/skins/common/media/images/openid/aol.gif
delete mode 100755 lms/askbot/skins/common/media/images/openid/blogger.ico
delete mode 100755 lms/askbot/skins/common/media/images/openid/claimid.ico
delete mode 100755 lms/askbot/skins/common/media/images/openid/facebook.gif
delete mode 100755 lms/askbot/skins/common/media/images/openid/flickr.ico
delete mode 100755 lms/askbot/skins/common/media/images/openid/google.gif
delete mode 100755 lms/askbot/skins/common/media/images/openid/livejournal.ico
delete mode 100755 lms/askbot/skins/common/media/images/openid/myopenid.ico
delete mode 100755 lms/askbot/skins/common/media/images/openid/openid-inputicon.gif
delete mode 100755 lms/askbot/skins/common/media/images/openid/openid.gif
delete mode 100755 lms/askbot/skins/common/media/images/openid/technorati.ico
delete mode 100755 lms/askbot/skins/common/media/images/openid/twitter.png
delete mode 100755 lms/askbot/skins/common/media/images/openid/verisign.ico
delete mode 100755 lms/askbot/skins/common/media/images/openid/vidoop.ico
delete mode 100755 lms/askbot/skins/common/media/images/openid/wordpress.ico
delete mode 100755 lms/askbot/skins/common/media/images/openid/yahoo.gif
delete mode 100644 lms/askbot/skins/common/media/images/print.png
delete mode 100644 lms/askbot/skins/common/media/images/pw-login.gif
delete mode 100755 lms/askbot/skins/common/media/images/quest-bg.gif
delete mode 100644 lms/askbot/skins/common/media/images/scopearrow.png
delete mode 100644 lms/askbot/skins/common/media/images/sprite.png
delete mode 100644 lms/askbot/skins/common/media/images/sprites.png
delete mode 100644 lms/askbot/skins/common/media/images/sprites_source/sprites.svg
delete mode 100644 lms/askbot/skins/common/media/images/summary-background.png
delete mode 100644 lms/askbot/skins/common/media/images/tag-left.png
delete mode 100644 lms/askbot/skins/common/media/images/tag-right.png
delete mode 100755 lms/askbot/skins/common/media/images/vote-accepted-on.png
delete mode 100755 lms/askbot/skins/common/media/images/vote-accepted.png
delete mode 100755 lms/askbot/skins/common/media/images/vote-arrow-down-on.png
delete mode 100755 lms/askbot/skins/common/media/images/vote-arrow-down.png
delete mode 100755 lms/askbot/skins/common/media/images/vote-arrow-up-on.png
delete mode 100755 lms/askbot/skins/common/media/images/vote-arrow-up.png
delete mode 100755 lms/askbot/skins/common/media/images/vote-favorite-off.png
delete mode 100755 lms/askbot/skins/common/media/images/vote-favorite-on.png
delete mode 100644 lms/askbot/skins/common/media/images/wiki.png
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/aol.gif
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/blogger-1.png
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/blogger.ico
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/claimid-0.png
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/claimid.ico
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/facebook.gif
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/flickr.ico
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/flickr.png
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/google.gif
delete mode 100644 lms/askbot/skins/common/media/jquery-openid/images/identica.png
delete mode 100644 lms/askbot/skins/common/media/jquery-openid/images/linkedin.gif
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/livejournal-1.png
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/livejournal.ico
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/myopenid-2.png
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/myopenid.ico
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/openid-inputicon.gif
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/openid.gif
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/openidico.png
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/openidico16.png
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/technorati-1.png
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/technorati.ico
delete mode 100644 lms/askbot/skins/common/media/jquery-openid/images/twitter.gif
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/verisign-2.png
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/verisign.ico
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/vidoop.ico
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/vidoop.png
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/wordpress.ico
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/wordpress.png
delete mode 100755 lms/askbot/skins/common/media/jquery-openid/images/yahoo.gif
delete mode 100644 lms/askbot/skins/common/media/jquery-openid/jquery.openid.js
delete mode 100644 lms/askbot/skins/common/media/jquery-openid/openid.css
delete mode 100644 lms/askbot/skins/common/media/js/autocompleter.js
delete mode 100644 lms/askbot/skins/common/media/js/compress.bat
delete mode 100644 lms/askbot/skins/common/media/js/editor.js
delete mode 100644 lms/askbot/skins/common/media/js/excanvas.min.js
delete mode 100644 lms/askbot/skins/common/media/js/flot-build.bat
delete mode 100644 lms/askbot/skins/common/media/js/jquery-1.4.3.js
delete mode 100644 lms/askbot/skins/common/media/js/jquery-fieldselection.js
delete mode 100644 lms/askbot/skins/common/media/js/jquery-fieldselection.min.js
delete mode 100644 lms/askbot/skins/common/media/js/jquery.ajaxfileupload.js
delete mode 100644 lms/askbot/skins/common/media/js/jquery.animate-colors.js
delete mode 100644 lms/askbot/skins/common/media/js/jquery.flot.js
delete mode 100644 lms/askbot/skins/common/media/js/jquery.flot.min.js
delete mode 100644 lms/askbot/skins/common/media/js/jquery.form.js
delete mode 100644 lms/askbot/skins/common/media/js/jquery.history.js
delete mode 100644 lms/askbot/skins/common/media/js/jquery.i18n.js
delete mode 100644 lms/askbot/skins/common/media/js/jquery.openid.js
delete mode 100644 lms/askbot/skins/common/media/js/jquery.validate.js
delete mode 100644 lms/askbot/skins/common/media/js/jquery.validate.min.js
delete mode 100644 lms/askbot/skins/common/media/js/jquery.validate.pack.js
delete mode 100644 lms/askbot/skins/common/media/js/less.min.js
delete mode 100644 lms/askbot/skins/common/media/js/live_search.js
delete mode 100644 lms/askbot/skins/common/media/js/live_search_new_thread.js
delete mode 100644 lms/askbot/skins/common/media/js/modernizr.custom.js
delete mode 100644 lms/askbot/skins/common/media/js/output-words.html
delete mode 100644 lms/askbot/skins/common/media/js/output-words.js
delete mode 100644 lms/askbot/skins/common/media/js/post.js
delete mode 100644 lms/askbot/skins/common/media/js/se_hilite.js
delete mode 100644 lms/askbot/skins/common/media/js/se_hilite_src.js
delete mode 100644 lms/askbot/skins/common/media/js/tag_selector.js
delete mode 100644 lms/askbot/skins/common/media/js/user.js
delete mode 100644 lms/askbot/skins/common/media/js/utils.js
delete mode 100644 lms/askbot/skins/common/media/js/wmd/images/editor-toolbar-background.png
delete mode 100755 lms/askbot/skins/common/media/js/wmd/images/wmd-buttons.png
delete mode 100644 lms/askbot/skins/common/media/js/wmd/showdown-min.js
delete mode 100644 lms/askbot/skins/common/media/js/wmd/showdown.js
delete mode 100644 lms/askbot/skins/common/media/js/wmd/wmd-min.js
delete mode 100644 lms/askbot/skins/common/media/js/wmd/wmd-test.html
delete mode 100644 lms/askbot/skins/common/media/js/wmd/wmd.css
delete mode 100644 lms/askbot/skins/common/media/js/wmd/wmd.js
delete mode 100644 lms/askbot/skins/common/media/style/auth.css
delete mode 100644 lms/askbot/skins/common/media/style/jquery.autocomplete.css
delete mode 100644 lms/askbot/skins/common/media/style/lib_style.less
delete mode 100644 lms/askbot/skins/common/media/style/openid.css
delete mode 100644 lms/askbot/skins/common/media/style/prettify.css
delete mode 100644 lms/askbot/skins/common/media/style/style.css
delete mode 100644 lms/askbot/skins/common/templates/authopenid/authopenid_macros.html
delete mode 100644 lms/askbot/skins/common/templates/authopenid/changeemail.html
delete mode 100644 lms/askbot/skins/common/templates/authopenid/complete.html
delete mode 100644 lms/askbot/skins/common/templates/authopenid/confirm_email.txt
delete mode 100644 lms/askbot/skins/common/templates/authopenid/email_validation.txt
delete mode 100644 lms/askbot/skins/common/templates/authopenid/logout.html
delete mode 100644 lms/askbot/skins/common/templates/authopenid/providers_javascript.html
delete mode 100644 lms/askbot/skins/common/templates/authopenid/signin.html
delete mode 100644 lms/askbot/skins/common/templates/authopenid/signup_with_password.html
delete mode 100644 lms/askbot/skins/common/templates/avatar/add.html
delete mode 100644 lms/askbot/skins/common/templates/avatar/change.html
delete mode 100644 lms/askbot/skins/common/templates/avatar/confirm_delete.html
delete mode 100644 lms/askbot/skins/common/templates/debug_header.html
delete mode 100644 lms/askbot/skins/common/templates/one_column_body.html
delete mode 100644 lms/askbot/skins/common/templates/question/answer_author_info.html
delete mode 100644 lms/askbot/skins/common/templates/question/answer_comments.html
delete mode 100644 lms/askbot/skins/common/templates/question/answer_controls.html
delete mode 100644 lms/askbot/skins/common/templates/question/answer_vote_buttons.html
delete mode 100644 lms/askbot/skins/common/templates/question/closed_question_info.html
delete mode 100644 lms/askbot/skins/common/templates/question/question_author_info.html
delete mode 100644 lms/askbot/skins/common/templates/question/question_comments.html
delete mode 100644 lms/askbot/skins/common/templates/question/question_controls.html
delete mode 100644 lms/askbot/skins/common/templates/question/question_tags.html
delete mode 100644 lms/askbot/skins/common/templates/question/question_vote_buttons.html
delete mode 100644 lms/askbot/skins/common/templates/question/share_buttons.html
delete mode 100644 lms/askbot/skins/common/templates/two_column_body.html
delete mode 100644 lms/askbot/skins/common/templates/widgets/edit_post.html
delete mode 100644 lms/askbot/skins/common/templates/widgets/related_tags.html
delete mode 100644 lms/askbot/skins/common/templates/widgets/search_bar.html
delete mode 100644 lms/askbot/skins/common/templates/widgets/tag_selector.html
delete mode 100644 lms/askbot/skins/loaders.py
delete mode 100644 lms/askbot/skins/mitx/media/images/accept.png
delete mode 100644 lms/askbot/skins/mitx/media/images/anon.png
delete mode 100644 lms/askbot/skins/mitx/media/images/answers-background.png
delete mode 100644 lms/askbot/skins/mitx/media/images/background-user-info.png
delete mode 100644 lms/askbot/skins/mitx/media/images/bigbutton.png
delete mode 100644 lms/askbot/skins/mitx/media/images/bigbuttonhover.png
delete mode 100755 lms/askbot/skins/mitx/media/images/blue-up-arrow-h18px.png
delete mode 100755 lms/askbot/skins/mitx/media/images/box-arrow.gif
delete mode 100755 lms/askbot/skins/mitx/media/images/bullet_green.gif
delete mode 100755 lms/askbot/skins/mitx/media/images/cc-88x31.png
delete mode 100644 lms/askbot/skins/mitx/media/images/cc-by-sa.png
delete mode 100644 lms/askbot/skins/mitx/media/images/close-small-dark.png
delete mode 100755 lms/askbot/skins/mitx/media/images/close-small-hover.png
delete mode 100755 lms/askbot/skins/mitx/media/images/close-small.png
delete mode 100644 lms/askbot/skins/mitx/media/images/close.png
delete mode 100644 lms/askbot/skins/mitx/media/images/comment-background.png
delete mode 100644 lms/askbot/skins/mitx/media/images/comment.png
delete mode 100644 lms/askbot/skins/mitx/media/images/contributorsback.png
delete mode 100755 lms/askbot/skins/mitx/media/images/dash.gif
delete mode 100644 lms/askbot/skins/mitx/media/images/delete.png
delete mode 100644 lms/askbot/skins/mitx/media/images/dialog-warning-off.png
delete mode 100644 lms/askbot/skins/mitx/media/images/dialog-warning.png
delete mode 100755 lms/askbot/skins/mitx/media/images/djangomade124x25_grey.gif
delete mode 100755 lms/askbot/skins/mitx/media/images/dot-g.gif
delete mode 100755 lms/askbot/skins/mitx/media/images/dot-list.gif
delete mode 100755 lms/askbot/skins/mitx/media/images/edit.png
delete mode 100644 lms/askbot/skins/mitx/media/images/edit2.png
delete mode 100644 lms/askbot/skins/mitx/media/images/email-sharing.png
delete mode 100755 lms/askbot/skins/mitx/media/images/expander-arrow-hide.gif
delete mode 100755 lms/askbot/skins/mitx/media/images/expander-arrow-show.gif
delete mode 100644 lms/askbot/skins/mitx/media/images/facebook-sharing.png
delete mode 100644 lms/askbot/skins/mitx/media/images/favicon.gif
delete mode 100644 lms/askbot/skins/mitx/media/images/favicon.ico
delete mode 100644 lms/askbot/skins/mitx/media/images/feed-icon-small.png
delete mode 100644 lms/askbot/skins/mitx/media/images/flag.png
delete mode 100644 lms/askbot/skins/mitx/media/images/go-up-grey.png
delete mode 100644 lms/askbot/skins/mitx/media/images/go-up-orange.png
delete mode 100644 lms/askbot/skins/mitx/media/images/google-plus-sharing.png
delete mode 100755 lms/askbot/skins/mitx/media/images/gray-up-arrow-h18px.png
delete mode 100755 lms/askbot/skins/mitx/media/images/grippie.png
delete mode 100755 lms/askbot/skins/mitx/media/images/indicator.gif
delete mode 100644 lms/askbot/skins/mitx/media/images/link.png
delete mode 100644 lms/askbot/skins/mitx/media/images/logo.gif
delete mode 100644 lms/askbot/skins/mitx/media/images/logo.png
delete mode 100755 lms/askbot/skins/mitx/media/images/logo1.png
delete mode 100755 lms/askbot/skins/mitx/media/images/logo2.png
delete mode 100644 lms/askbot/skins/mitx/media/images/lrg/email-sharing.png
delete mode 100644 lms/askbot/skins/mitx/media/images/lrg/facebook-sharing.png
delete mode 100644 lms/askbot/skins/mitx/media/images/lrg/facebook.png
delete mode 100644 lms/askbot/skins/mitx/media/images/lrg/google-plus-sharing.png
delete mode 100644 lms/askbot/skins/mitx/media/images/lrg/linkedin.png
delete mode 100644 lms/askbot/skins/mitx/media/images/lrg/twitter-sharing.png
delete mode 100644 lms/askbot/skins/mitx/media/images/lrg/twitter.png
delete mode 100644 lms/askbot/skins/mitx/media/images/lrg/youtube-sharing.png
delete mode 100644 lms/askbot/skins/mitx/media/images/mail-envelope-empty.png
delete mode 100644 lms/askbot/skins/mitx/media/images/mail-envelope-full.png
delete mode 100755 lms/askbot/skins/mitx/media/images/medala.gif
delete mode 100755 lms/askbot/skins/mitx/media/images/medala_on.gif
delete mode 100644 lms/askbot/skins/mitx/media/images/medium-button.png
delete mode 100755 lms/askbot/skins/mitx/media/images/new.gif
delete mode 100755 lms/askbot/skins/mitx/media/images/nophoto.png
delete mode 100644 lms/askbot/skins/mitx/media/images/notification.png
delete mode 100755 lms/askbot/skins/mitx/media/images/openid.gif
delete mode 100644 lms/askbot/skins/mitx/media/images/print.png
delete mode 100644 lms/askbot/skins/mitx/media/images/pw-login.gif
delete mode 100755 lms/askbot/skins/mitx/media/images/quest-bg.gif
delete mode 100644 lms/askbot/skins/mitx/media/images/retag.png
delete mode 100644 lms/askbot/skins/mitx/media/images/scopearrow.png
delete mode 100644 lms/askbot/skins/mitx/media/images/small-button-blue.png
delete mode 100644 lms/askbot/skins/mitx/media/images/small-button-cancel.png
delete mode 100644 lms/askbot/skins/mitx/media/images/social/email-sharing.png
delete mode 100644 lms/askbot/skins/mitx/media/images/social/facebook-sharing.png
delete mode 100644 lms/askbot/skins/mitx/media/images/social/google-plus-sharing.png
delete mode 100644 lms/askbot/skins/mitx/media/images/social/twitter-sharing.png
delete mode 100644 lms/askbot/skins/mitx/media/images/social/youtube-sharing.png
delete mode 100644 lms/askbot/skins/mitx/media/images/socialsprite.png
delete mode 100644 lms/askbot/skins/mitx/media/images/sprite.png
delete mode 100644 lms/askbot/skins/mitx/media/images/sprites.png
delete mode 100644 lms/askbot/skins/mitx/media/images/sprites_source/graphics.svg
delete mode 100644 lms/askbot/skins/mitx/media/images/sprites_source/sprites.svg
delete mode 100644 lms/askbot/skins/mitx/media/images/summary-background.png
delete mode 100644 lms/askbot/skins/mitx/media/images/tag-left.png
delete mode 100644 lms/askbot/skins/mitx/media/images/tag-right.png
delete mode 100644 lms/askbot/skins/mitx/media/images/tips.png
delete mode 100644 lms/askbot/skins/mitx/media/images/twitter-sharing.png
delete mode 100644 lms/askbot/skins/mitx/media/images/view-background.png
delete mode 100755 lms/askbot/skins/mitx/media/images/vote-accepted-on.png
delete mode 100755 lms/askbot/skins/mitx/media/images/vote-accepted.png
delete mode 100644 lms/askbot/skins/mitx/media/images/vote-arrow-down-new.png
delete mode 100644 lms/askbot/skins/mitx/media/images/vote-arrow-down-on-new.png
delete mode 100755 lms/askbot/skins/mitx/media/images/vote-arrow-down-on.png
delete mode 100755 lms/askbot/skins/mitx/media/images/vote-arrow-down.png
delete mode 100644 lms/askbot/skins/mitx/media/images/vote-arrow-up-new.png
delete mode 100644 lms/askbot/skins/mitx/media/images/vote-arrow-up-on-new.png
delete mode 100755 lms/askbot/skins/mitx/media/images/vote-arrow-up-on.png
delete mode 100755 lms/askbot/skins/mitx/media/images/vote-arrow-up.png
delete mode 100644 lms/askbot/skins/mitx/media/images/vote-background.png
delete mode 100755 lms/askbot/skins/mitx/media/images/vote-favorite-off.png
delete mode 100755 lms/askbot/skins/mitx/media/images/vote-favorite-on.png
delete mode 100644 lms/askbot/skins/mitx/media/images/wiki.png
delete mode 100644 lms/askbot/skins/mitx/media/images/youtube-sharing.png
delete mode 100644 lms/askbot/skins/mitx/media/style/auth.css
delete mode 100644 lms/askbot/skins/mitx/media/style/extra.css
delete mode 100644 lms/askbot/skins/mitx/media/style/jquery.autocomplete.css
delete mode 100644 lms/askbot/skins/mitx/media/style/lib_style.less
delete mode 100644 lms/askbot/skins/mitx/media/style/openid.css
delete mode 100644 lms/askbot/skins/mitx/media/style/prettify.css
delete mode 100644 lms/askbot/skins/mitx/media/style/style.css
delete mode 100644 lms/askbot/skins/mitx/media/style/style.less
delete mode 100644 lms/askbot/skins/mitx/templates/404.html
delete mode 100644 lms/askbot/skins/mitx/templates/404.jinja.html
delete mode 100644 lms/askbot/skins/mitx/templates/500.html
delete mode 100644 lms/askbot/skins/mitx/templates/500.jinja.html
delete mode 100644 lms/askbot/skins/mitx/templates/answer_edit.html
delete mode 100644 lms/askbot/skins/mitx/templates/ask.html
delete mode 100644 lms/askbot/skins/mitx/templates/badge.html
delete mode 100644 lms/askbot/skins/mitx/templates/badges.html
delete mode 100644 lms/askbot/skins/mitx/templates/base.html
delete mode 100644 lms/askbot/skins/mitx/templates/close.html
delete mode 100644 lms/askbot/skins/mitx/templates/course_navigation.jinja.html
delete mode 100644 lms/askbot/skins/mitx/templates/faq_static.html
delete mode 100644 lms/askbot/skins/mitx/templates/feedback.html
delete mode 100644 lms/askbot/skins/mitx/templates/feedback_email.txt
delete mode 100644 lms/askbot/skins/mitx/templates/help.html
delete mode 100644 lms/askbot/skins/mitx/templates/import_data.html
delete mode 100644 lms/askbot/skins/mitx/templates/instant_notification.html
delete mode 100644 lms/askbot/skins/mitx/templates/macros.html
delete mode 100644 lms/askbot/skins/mitx/templates/main_page.html
delete mode 100644 lms/askbot/skins/mitx/templates/main_page/content.html
delete mode 100644 lms/askbot/skins/mitx/templates/main_page/headline.html
delete mode 100644 lms/askbot/skins/mitx/templates/main_page/javascript.html
delete mode 100644 lms/askbot/skins/mitx/templates/main_page/nothing_found.html
delete mode 100644 lms/askbot/skins/mitx/templates/main_page/paginator.html
delete mode 100644 lms/askbot/skins/mitx/templates/main_page/questions_loop.html
delete mode 100644 lms/askbot/skins/mitx/templates/main_page/scope_filters.html
delete mode 100644 lms/askbot/skins/mitx/templates/main_page/sidebar.html
delete mode 100644 lms/askbot/skins/mitx/templates/main_page/tab_bar.html
delete mode 100644 lms/askbot/skins/mitx/templates/meta/bottom_scripts.html
delete mode 100644 lms/askbot/skins/mitx/templates/meta/editor_data.html
delete mode 100644 lms/askbot/skins/mitx/templates/meta/html_head_javascript.html
delete mode 100644 lms/askbot/skins/mitx/templates/meta/html_head_meta.html
delete mode 100644 lms/askbot/skins/mitx/templates/meta/html_head_stylesheets.html
delete mode 100644 lms/askbot/skins/mitx/templates/meta/mandatory_tags_js.html
delete mode 100644 lms/askbot/skins/mitx/templates/navigation.jinja.html
delete mode 100644 lms/askbot/skins/mitx/templates/question.html
delete mode 100644 lms/askbot/skins/mitx/templates/question/answer_card.html
delete mode 100644 lms/askbot/skins/mitx/templates/question/answer_tab_bar.html
delete mode 100644 lms/askbot/skins/mitx/templates/question/content.html
delete mode 100644 lms/askbot/skins/mitx/templates/question/javascript.html
delete mode 100644 lms/askbot/skins/mitx/templates/question/new_answer_form.html
delete mode 100644 lms/askbot/skins/mitx/templates/question/question_card.html
delete mode 100644 lms/askbot/skins/mitx/templates/question/sharing_prompt_phrase.html
delete mode 100644 lms/askbot/skins/mitx/templates/question/sidebar.html
delete mode 100644 lms/askbot/skins/mitx/templates/question/subscribe_by_email_prompt.html
delete mode 100644 lms/askbot/skins/mitx/templates/question_edit.html
delete mode 100644 lms/askbot/skins/mitx/templates/question_retag.html
delete mode 100644 lms/askbot/skins/mitx/templates/question_widget.html
delete mode 100644 lms/askbot/skins/mitx/templates/reopen.html
delete mode 100644 lms/askbot/skins/mitx/templates/revisions.html
delete mode 100644 lms/askbot/skins/mitx/templates/static_page.html
delete mode 100644 lms/askbot/skins/mitx/templates/subscribe_for_tags.html
delete mode 100644 lms/askbot/skins/mitx/templates/tags.html
delete mode 100644 lms/askbot/skins/mitx/templates/user_profile/user.html
delete mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_edit.html
delete mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_email_subscriptions.html
delete mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_favorites.html
delete mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_inbox.html
delete mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_info.html
delete mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_moderate.html
delete mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_network.html
delete mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_recent.html
delete mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_reputation.html
delete mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_stats.html
delete mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_tabs.html
delete mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_votes.html
delete mode 100644 lms/askbot/skins/mitx/templates/user_profile/users_questions.html
delete mode 100644 lms/askbot/skins/mitx/templates/users.html
delete mode 100644 lms/askbot/skins/mitx/templates/widgets/answer_edit_tips.html
delete mode 100644 lms/askbot/skins/mitx/templates/widgets/ask_button.html
delete mode 100644 lms/askbot/skins/mitx/templates/widgets/ask_form.html
delete mode 100644 lms/askbot/skins/mitx/templates/widgets/contributors.html
delete mode 100644 lms/askbot/skins/mitx/templates/widgets/footer.html
delete mode 100644 lms/askbot/skins/mitx/templates/widgets/header.html
delete mode 100644 lms/askbot/skins/mitx/templates/widgets/logo.html
delete mode 100644 lms/askbot/skins/mitx/templates/widgets/meta_nav.html
delete mode 100644 lms/askbot/skins/mitx/templates/widgets/question_edit_tips.html
delete mode 100644 lms/askbot/skins/mitx/templates/widgets/question_summary.html
delete mode 100644 lms/askbot/skins/mitx/templates/widgets/scope_nav.html
delete mode 100644 lms/askbot/skins/mitx/templates/widgets/secondary_header.html
delete mode 100644 lms/askbot/skins/mitx/templates/widgets/system_messages.html
delete mode 100644 lms/askbot/skins/mitx/templates/widgets/user_list.html
delete mode 100644 lms/askbot/skins/mitx/templates/widgets/user_long_score_and_badge_summary.html
delete mode 100644 lms/askbot/skins/mitx/templates/widgets/user_navigation.html
delete mode 100644 lms/askbot/skins/mitx/templates/widgets/user_score_and_badge_summary.html
delete mode 100644 lms/askbot/skins/utils.py
delete mode 100644 lms/envs/askbotsettings.py
diff --git a/.gitignore b/.gitignore
index 81d9a57d3c..1dd3b77523 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,8 +9,6 @@
.AppleDouble
database.sqlite
courseware/static/js/mathjax/*
-db.newaskbot
-db.oldaskbot
flushdb.sh
build
.coverage
diff --git a/.gitmodules b/.gitmodules
index 72ec77d0e2..e69de29bb2 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +0,0 @@
-[submodule "askbot"]
- path = askbot
- url = git@github.com:MITx/askbot-devel.git
diff --git a/.hgignore b/.hgignore
deleted file mode 100644
index 67ea339cd8..0000000000
--- a/.hgignore
+++ /dev/null
@@ -1,12 +0,0 @@
-syntax: glob
-*.pyc
-*~
-*.scssc
-*.swp
-*.orig
-*.DS_Store
-database.sqlite
-courseware/static/js/mathjax/*
-db.newaskbot
-db.oldaskbot
-flushdb.sh
diff --git a/askbot b/askbot
deleted file mode 160000
index e56ae38084..0000000000
--- a/askbot
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit e56ae380846f7c6cdaeacfc58880fab103540491
diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py
index 61c2537399..0eded21df1 100644
--- a/common/djangoapps/student/models.py
+++ b/common/djangoapps/student/models.py
@@ -3,6 +3,8 @@ Models for Student Information
Replication Notes
+TODO: Update this to be consistent with reality (no portal servers, no more askbot)
+
In our live deployment, we intend to run in a scenario where there is a pool of
Portal servers that hold the canoncial user information and that user
information is replicated to slave Course server pools. Each Course has a set of
diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py
index dd0df2125a..2b2e709bcb 100644
--- a/common/lib/xmodule/xmodule/x_module.py
+++ b/common/lib/xmodule/xmodule/x_module.py
@@ -388,7 +388,7 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates):
entry_point = "xmodule.v1"
module_class = XModule
- # Attributes for inpsection of the descriptor
+ # Attributes for inspection of the descriptor
stores_state = False # Indicates whether the xmodule state should be
# stored in a database (independent of shared state)
has_score = False # This indicates whether the xmodule is a problem-type.
diff --git a/create-dev-env.sh b/create-dev-env.sh
index 0d7e392313..e481d3fd5e 100755
--- a/create-dev-env.sh
+++ b/create-dev-env.sh
@@ -72,12 +72,6 @@ clone_repos() {
git clone git@github.com:MITx/mitx.git
fi
- if [[ ! -d "$BASE/mitx/askbot/.git" ]]; then
- output "Cloning askbot as a submodule of mitx"
- cd "$BASE/mitx"
- git submodule update --init
- fi
-
# By default, dev environments start with a copy of 6.002x
cd "$BASE"
mkdir -p "$BASE/data"
@@ -334,9 +328,6 @@ pip install -r mitx/pre-requirements.txt
output "Installing MITx requirements"
cd mitx
pip install -r requirements.txt
-output "Installing askbot requirements"
-pip install -r askbot/askbot_requirements.txt
-pip install -r askbot/askbot_requirements_dev.txt
mkdir "$BASE/log" || true
mkdir "$BASE/db" || true
diff --git a/doc/overview.md b/doc/overview.md
index 36e22e16eb..f64d12920d 100644
--- a/doc/overview.md
+++ b/doc/overview.md
@@ -27,7 +27,7 @@ You should be familiar with the following. If you're not, go read some docs...
- CMS -- Course Management System. The instructor-facing parts of the system. Allows instructors to see and modify their course, add lectures, problems, reorder things, etc.
- - Askbot -- the discussion forums. We have a custom fork of this project. We're also hoping to replace it with something better later. (e.g. need support for multiple classes, etc)
+ - Forums -- this is a ruby on rails service that runs on Heroku. Contributed by berkeley folks. The LMS has a wrapper lib that talks to it.
- Data. In the data/ dir. There is currently a single `course.xml` file that describes an entire course. Speaking of which...
diff --git a/install.txt b/install.txt
index 37a6e50986..801036af6b 100644
--- a/install.txt
+++ b/install.txt
@@ -9,7 +9,6 @@ There is also a script "create-dev-env.sh" that automates these steps.
mkdir ~/mitx_all
cd ~/mitx_all
git clone git@github.com:MITx/mitx.git
- git clone git@github.com:MITx/askbot-devel
hg clone ssh://hg-content@gp.mitx.mit.edu/data
2) Install OSX dependencies (Mac users only)
@@ -49,8 +48,6 @@ There is also a script "create-dev-env.sh" that automates these steps.
source ~/mitx_all/python/bin/activate
cd ~/mitx_all
- pip install -r askbot-devel/askbot_requirements.txt
- pip install -r askbot-devel/askbot_requirements_dev.txt
pip install -r mitx/pre-requirements.txt
pip install -r mitx/requirements.txt
diff --git a/lms/askbot/skins/README b/lms/askbot/skins/README
deleted file mode 100644
index 3fbc8c331e..0000000000
--- a/lms/askbot/skins/README
+++ /dev/null
@@ -1,71 +0,0 @@
-=============================
-Customization of Askbot skins
-=============================
-
-The default skin at the moment is in the development, however
-it is already possible to start customizing your site without
-incurring much maintenance overhead.
-
-Current status of templates
-===========================
-The two busiest templates are - the "main" page and the "question" page,
-the main page is more or less complete. "Question" page will be significantly
-refactored in the near future.
-
-How skins work in Askbot
-========================
-
-The skins reside in up to two directories:
-
-* `askbot/skins` in the source code (contains any stock skins)
-* directory pointed to by a ASKBOT_EXTRA_SKINS_DIR in your settings.py
- with any other skins
-
-Currently, the skin is selected by the site administrator in the live settings.
-Also, at the moment skin default is special - it serves any resources
-absent in other skins. In a way - all other skins inherit from the "default".
-
-Templates and media are resolved in the following way:
-* check in skin named as in settings.ASKBOT_DEFAULT_SKIN
-* then skin named 'default'
-
-How to customize a skin
-=======================
-
-There are three options:
-
-* edit custom css via the settings interface - good for small tweaks
- (no need to directly log in to the server)
-* create a new skin in separate files (need direct access to the server
- files, more maintenance overhead)
-* directly modify the "default" skin (as in the previous option - need
- direct access to the server, less maintenance overhead, some
- knowledge of git system is required)
-
-The first option only allows to modify css and add custom javascript.
-The latter two options allow changing the templates as well.
-
-If you wish to follow the second option, create a directory named the same
-way as the skin you are building and start adding files with the same names
-and relative locations as those in the "default" skin.
-
-NO NEED TO CREATE ALL TEMPLATES/MEDIA FILES AT ONCE as your skin will inherit
-pieces from the "default".
-
-The disadvantage of thil second approach is that you will be on your own maintaining
-the synchrony of your template, stylesheet and the core code.
-
-Third approach is the best, but it requires (the most basic) use of
-git source code management software. With git you will easily merge the updates
-from the development repository.
-
-Structure of the skin directories
-=================================
-Todo.
-
-To simplify maintenance of the css as the skin is being developed,
-populate css file `media/style/extra.css` with any rules that will
-override those in the `media/style/style.css` file. If you do that
-
-media does not have to be composed of files named the same way as in default skin
-whatever media you link to from your templates - will be in operation
diff --git a/lms/askbot/skins/__init__.py b/lms/askbot/skins/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/lms/askbot/skins/common/media/images/anon.png b/lms/askbot/skins/common/media/images/anon.png
deleted file mode 100644
index a2041590216dc4ed8f86195dcdfc66718bb47c39..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 687
zcmV;g0#N;lP)XC>R=F^NKk_!2rAkH3rS@uh-eipOd3a}2O7i~W9ICor4Muuv$VD2f=5$1#`7
z;b1V(Vb<$4@;v{01J!C(N8D^S(QdbY!&xjAEEEbltSrmOvMjv;o6Uy%{hknxMkBOZ
zt%ij4dR=Ei6h(RisZ>fwCP~uWC3QF)cs`#=PqA1u#2e0mbUIDy)9D1=Zr2D7008^_
z9#mB&&yh&P5N|jKI-L$or&Hj09u9{C9FNDl1^@sUh9ULka=D8)b7$|S+wI2d^-8v&
zTrU594m_F+!(gk`BI38(4MU;O>kQOtHQk|YG#ZceJx9D$D(S=(ML~|^USptAsp!O2
zRYjlA_h|ofmt9d5^m@H!IOxSCN%|)4@p#Pg(23jacAU*-M0`9R(}>d>$Y!&;w~xhQ
zR6Nw5WQjzA)WhKrWLc)-p*G<6`$;{Y&uMAs4Y*t`Qm>9R#xxjYc652mk=U
zbUKB}Wb#@A&x(KVzu-@Ne0-3DsA(ExG8t+URQGqWSdba8TrR1n>D#y9?FRnef=}bN
VTfTAXK@I=_002ovPDHLkV1g0tCr|(Y
diff --git a/lms/askbot/skins/common/media/images/bigbutton.png b/lms/askbot/skins/common/media/images/bigbutton.png
deleted file mode 100644
index 2a7c0f05858e26e279fe52242d4d2acf7b8926c8..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 263
zcmeAS@N?(olHy`uVBq!ia0vp^Y(N~s0U{T6xorVbEX7WqAsj$Z!;#Vf2?p
zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4&$zvn9~6EtZ`^i_Kc&@Uqr$fxhx0DITbMW_t+i0)Y0gW5
z111hvt|`7MYYnLJpblKlqWG(SPyyip4(M3kDZRAec-nq>lf8G9ZE1RYT_qu4I
z8CFZ)M+vtSM*8|Ev;Tc?xZywBZMoICUz@caF#RjdJJeUgGZp9_22WQ%mvv4FO#q;5
BUts_M
diff --git a/lms/askbot/skins/common/media/images/bigbuttonhover.png b/lms/askbot/skins/common/media/images/bigbuttonhover.png
deleted file mode 100644
index cf4bacca69ffc4736c0c1ec25795647bf7bec72e..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 236
zcmeAS@N?(olHy`uVBq!ia0vp^Y(T8d0V1RS{agT~Sc;uILpXq-h9ji|$mcBZh%5%G
zzYfBTP8zc-fP#`Gt`Q}{`DrEPiAAXl<>lpinR(g8$%zH2dih1^v)|cB0TpF>x;Tbd
z^sb$7k+;D>!0F$d~L-N
zvOJSrlNlb$zAV1{^xDV5&|PgTe~DWM4f!;x4L
diff --git a/lms/askbot/skins/common/media/images/blue-up-arrow-h18px.png b/lms/askbot/skins/common/media/images/blue-up-arrow-h18px.png
deleted file mode 100755
index e1f29e86334ce72d2d28989a133571d7bf53a94e..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 593
zcmV-X0{6iqad+$V|KR(Q{q;Ok;ua!jjQtuI1z7)O(
zySE1i5k-TnUMhU^!Fth&L`=(4hnCB&mew0>atrF^~@9muPo$nmJ5BNLwb>CS+
z;4aaxR`*WD1Hfmk^?b25_^vW#;gMP0a=;-lw8uKYF+TucXlk2wNsBS@}pFD
ztaavBN$@-;DyO5Dqi0!d9a~=U7;O0Ayiuq>#Jyi<@Hb@35~1ra$v5dmgDzqB>^+=uOM7b5+>fY}+kwy1R`cV__*wFuQ!Bi@#VE~BJd
f7UnWh{5Sjn&bP0xmAf5500000NkvXXu0mjfU!M$-
diff --git a/lms/askbot/skins/common/media/images/box-arrow.gif b/lms/askbot/skins/common/media/images/box-arrow.gif
deleted file mode 100755
index 89dcf5b3dd40fac0e6afb0b1a7ff899a059f923f..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 69
zcmZ?wbhEHbWM@!dXkcJ?_wL=lfBzJJvM@6+Ff!;c00Bsbfk~#Pf92`7{EJz*0#eqW
WoE7iinDij`X5pe+YFj-S8LR>Nkr%B1
diff --git a/lms/askbot/skins/common/media/images/bullet_green.gif b/lms/askbot/skins/common/media/images/bullet_green.gif
deleted file mode 100755
index fa530910f9dc11fadaa2314f72bd98f29df39daf..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 64
zcmZ?wbhEHbKLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T
zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p
z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&nehQ1i
z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW
zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X
zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4
zfg=2N-7=cNnjjOr{yriy6mMFgG#l
znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U
zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya?
z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y
zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB
zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt
z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C
z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB
zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe
zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0
z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$
z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4
z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu
zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu
z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E
ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw
zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX
z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i&
z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01
z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R
z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw
zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD
zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3|
zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy
zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z
zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=00004XF*Lt006O$eEU(80000WV@Og>004R=004l4008;_004mL004C`
z008P>0026e000+nl3&F}000VaNklJyRN8J52lge3Dk<@*>AXrMmGP=H
z$s9P2&*V2vPMX-1L}oIXPMP_nlH^n>#fZ%$`c@)Y4M0h*N)5H*Dy5UhPr>W;VqjnZ
zld(w!&=SoOtX;bn<{y|*_V7bUrc~hhgft*Y9vryzSY}M06iG7y={dwn-rQt#5xb>+`RC^f6v^z4(Q|d8B-!MDTk(XP{Q8r-@ZB!XmXA&5bSa9P_XS
zTCEm>AV9CzqqMXX0MOIZgGeL-kH>?Fi3wI$un4HD--X4uEk+`dKs=E^A`wR-kx-fp
zO5tksDt`IIQ;5Z45Cj2ErxSX;UeVb|B!b%7T6jDjXfztMI$CGF)V0_(RPU_$!r;hu
zn9lZ|rQW`C)aUP`!JwZmydNaNB2aqrl2)swmX;QZMx!*Bbai!+R;y+0iVQ_`zOR=C
zgM;K7?4#bkbL8zkOJ~GxdiQVd(CO||bmE=kWH1;A09mb81)hvE^BFSsTgU&bNJWOC
ztT-;GleTW!d{a21qod^O>my%ZAB~QV=7j@*cmPnPZLnIUf?B22T%*y5Kp=qT=4NQM
z+BwaG)oMjJ9A;B#Bs79{#}S1s=b1+$c_V(GAE8hPf*_!N_wIR3kkx91AP5)f1LhU0p{MZ^CXrv4!x
zo%i*U_q?RV@4WXHG89YN8xDu_#t(4No=U&Jo}T#mV+4R&
zT3YDP!9xUqj=uF)UN~}pe0_Zq-ks~{XWMO5zpIW`EYs79WqNWsojGx`Xt1Qkj%OUq
z-gb3$&8xN9M61=p>-Dm>jzf;LFP+w4F6~qEJVKX3FqurVY@E$zV_I&r*%W1Zy&fi$
zNwKnrLK03{Ss5PLxDjJxV>r{@jTOuE`0wO3Y}U!qMyXh{a+E1ug;r
z%7vfc5AII1wI9abrbYmOyR$1Njx4Ng@pzP#D|w7a2#pJ(xpq`?(9P7
z(xtp`WNCd?Qn=s$@fCLONB7?k0Eoq6`1s0|oH&eNS)06GFC(YAx|&rIMG+Q@1!l7u
zX0sV~yFF)FM@NUkUdmeRJ?qWTUJe{TP4H(Sciwdu+S(6ebaWK;yXsK#z1#8j(W5AP
za099~Z(%lcF41T-aJgK#O|t|qzx;C6I-h)UeNGH%qrq|DISvB@1Com+2#gp}6k#@-
z6^%qugeZ!ruC9i~VwqKT;lc%Ub#*Z;x7&?@fdOd;m?l;tmt%fqw#7`=T&vYOt2|qq
znfGF`7|wKeqpkfgte0#kdvF5)px@_1zt4xl!a{J1)N|r2Qmav4SBKjcFJ|}peLj|7
zreBt`KN%V&|pt!ggq9`H~i6}ZI575lK?1PG;2!p|(*jdfQ`Nd<8qjleYth#+Asy1)I
zhnFL$sH{X=`(adV+m;v3*w`3anwqh7(`K~p+mF_L`_a_U007v%XHU-lCm%bWIiI-6)J@3jZHeAiPP48
z7`7cdaOL0s!au`dxSUSxZE8eCWhLwf_M>FY>UnW&J9fafV}~@*ot^0F?8Nm?u46;l
zL)cQaHD`YmCgSsyb?eu`P;7w5^H-xjdsS!VZ-~n8|ENOLdaWM?V#dG2)HJFr9tW0|%YqQ(!#>B(~
zg25n`FJBJ3-OkG7ZE|K==5(HpkFp;{CmLHCq0wmIcDrG*SXi6PC|e{FfyH7$M@I)V
z8Vz<<@0|5AdwyG=6=&D(I=ueo8#jfsdd+H7R92#*vJ!^k;=FKJu*W|x
zk_klrGl7HlgUpvS8jUa-jnHbf5Jiy<`g}o}Yq4u+JLq6Rn$Hu51$+93`cY6&0DF@i
zo?{+pwOT}@QH5vVa=BPA$7C|0xw)AQfXn4#bu-HVVEOXph(sc|?}zu{h_fBnr=}2}
zL9iS*P9;<$H0nVR0kLsr+|JUhsq9(&;-H5o|Zng_MaPR;g
z-0%RV;!~K4$B~G~5l_q@I7^9PX4ba;*Y)WDdvcoO
zj3Xnq$kMHKWW*MC0MyXjK>pzY3SJD*qd$FA5m5^S0t2x}BJ@2C6#Q+Tk{q)lBUdp@e%wwXN`OJIs
z;mp@QAFOP37K$-TgJr3Cx*Ap6s@PGF=0
zojBKXZow~eGp1oalQ0+lLI*8hj5!IG<=)bJv7u}O?pS>XzM;{iKb@L3Fhqo_u^5I1
zhj9Aj=>-$}S!wXi+Ti)tginp&ujD2+8btKKLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T
zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p
z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&nehQ1i
z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW
zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X
zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4
zfg=2N-7=cNnjjOr{yriy6mMFgG#l
znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U
zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya?
z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y
zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB
zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt
z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C
z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB
zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe
zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0
z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$
z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4
z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu
zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu
z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E
ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw
zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX
z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i&
z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01
z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R
z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw
zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD
zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3|
zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy
zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z
zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=00004XF*Lt006O$eEU(80000WV@Og>004R=004l4008;_004mL004C`
z008P>0026e000+nl3&F}000Q~Nkl0x(=yA<`H9zo;y7lu1KkAHW+`R1R0{t-h6aWM?@6`shN
z3n7GI01yDUaq~J@mIceQ;5ZH}%YkEAu$*~iSuhL>mSMm!48Sm!77!2+oj@l#blpgr
zrWsdVgQn@wb&b~PM5pZuA=b;#{No@8_a6cPFaU@JF2`|3V!2YDg=rjJgHnE-PRF^hwHZ!8V@V;R|O)=}>9cpx2+
zuyx0lRe`Zy`WXP^lUx4)&+`;ip689k@fcP{(08s6Q&UsRQs{QO(f+%3w6yGks%g+P
z6{>0`O@*pzl;M?_T+ZSL|M?OZ;}g{rD3swz~q2vt>`Cc^?)H*L8M=&uW3BXlB+LZJXjk`Rl<
z(9qD}_=tQykG8fp$g&KV%Z1*)-erTZ-ufxPxBhhtH5RZnJZktF2e^VDARG=O6beBQ
zgas8Q5(%`mwNap(JWY6i;BWX|Z7o!_h+?rwnOqU%GJD+lW)~*169@zX7#tidO*_-H
z*qNS4BoGV+(d22unR7kM0-%!q$;nAfO->rKvA!Pl_4O;(e{laHumHfBHkju)@SKym
zq9`JrP9qYDEDA0F1Ofr%aydH1CbAPa-*>@b%USA?Fr{Sv*#W#amc~SO0+J-5{nV)?
zKPC_eK$0X(WG8TMEWIq7Tdx1_moLNn{0lgG_y~?3J_7IaFW}RgH&>+J5*7eh%Pr$L
z3@alJaFfa8ylJzT2!eoQGD$&}hhz*7kAUO&${@3r+Xn~8^2f$I#9}dcnw!i0%ZdwP
zEEaS0T};aLkKVqG?yd;xpLrHuuMY=WTLA#S{?%*vQ-6Qe`W*lp3^rKYcdm~DH8?o9
zBx@G~+3MT(hd$GnHo;{~pPDs23vuBP{P=5Eu2XLz91c?!u0#Yu
zpd&stHHB+euhR{S0rJ8o!jekQPb3l*^NaOaNbGHU-!+K!*AG9SWj}uDB>+I7P{5ZD
z9#pNL0&KAOFfRLXF%B#8o}C<=T&pQDqaC{Ppy
zp->2Zzkgoc`1m+ol_g0+B9TBUl`?jKHei7gxFxT=q$`ujR9ws~<^}?RdG(e0EF|`}
zH9U{@lP9sUww9KS-o8!q&ow+(wSF6ejSsbUXqz{0rVZNK+Nk}><#KeOgbPr26#DQwYBEB#s4%bosxJ6tXo
z5{U%-em~V^_k;8KJp6t?`uqFga=CCKbYj^bUrGP=9ouoBwG{_iTj6PLUX^~zVC#kJ
zTy>Z&%wl|eoHmF=BJ|;#Hf?eMXVqmgnR#`VSi0(5bs)sH9R`6lrsmzemwU?f6yg{A
z@y^?QkYyRNEJGAUh@uEV5TGauCBsUBG3%|LuG^z`N3mhU21L7}kcVUlf`IAiX@_SJ
zkH=|@!Rz%R5{Xdu#^Z6?)?Nnyjg5`S=ks{+M=#<+>^vUL&7oMFLs2cFSX7}|aS2_6
zuIV%gU%pfOHij!{u;=!jf1{>m13nx1458nI=(aE%4zKE^tpEuCoPGN&e!6Ej=8AKe
zD;A*^izupP09Oe3eA@%=EC70>N6hVZllY(GBy&Ga?qtTudw+Rv)hr-{
zESk5Mtyce1&s;TwEeXF7!qHzGrKb=|au`>yTwV0oexI}tUT+IMYrAsgD$YjFu6~iI
zz^=7oZuv7Ro%gMV#mr*+)~(yNpfl2i`X9J$rxEsWBOHFK^^Yf0laqM&d<-MQBWtq?
z+dy1v)3DI97JKHJ0c^>xmR;DhcMrC0--hqIT+SG-=~{hNDB$j$ySQ=f#+m{9O)+>W
x8^7)VKarISLkQW(FwB3SK(P)R3__Uy4FK>(yR3Yb!O;K!002ovPDHLkV1lxPh(-Va
diff --git a/lms/askbot/skins/common/media/images/close-small-dark.png b/lms/askbot/skins/common/media/images/close-small-dark.png
deleted file mode 100755
index 280c1fc74e47c0e7d1c68d6f356eb22eeba7a2de..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 226
zcmeAS@N?(olHy`uVBq!ia0vp^d?3ui3?$#C89V|~1_3@Hu3NY7ShRS_jG43c?mKYe
z;M6!dxb8-^+kP+MX_sAsp9}6ArM>V3CjzZ7^A^)I5Q~
t!yzC*Q0L$Zl?Eqn$0Lca5`Jx9W=NmTc;}pLNdZtBgQu&X%Q~loCIHsxKFr1QCT0q{1{MYeDE)e-c@Ne1ia=5ZCX&{{Hy&_tW!N_ZKfc
zpObUM#pTWAs~3xk4w#ty`t$F>lBGXhy}DLgdpr1Q#zqRd1{MYe;g^zIfZEtgJbhi+Z*Z~+S_nO6;N1-r
z;wE)e-c@Ne1ia=5Z4(qXDwR1Wb5`Fd-okU
zaq{%lYd1c8`uyw9U!Xizkhgxu4WI-^NswRge+Xc>*St3pD5mS_;uunKD_NtRfsJRE
z&YnFcvL4forc1XpAK{H{IrvG{>z#&4h|Ds+W&FE$GCZx(SSr0JY6i#_Pgg&ebxsLQ
E08gA+X#fBK
diff --git a/lms/askbot/skins/common/media/images/contributorsback.png b/lms/askbot/skins/common/media/images/contributorsback.png
deleted file mode 100644
index dd72838396a66be23b99c4f3dac2e22a07e5f124..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 714
zcmV;*0yX`KP)!S<-E*`}w|bpTgd0gjDDs@DtguF7w88g%!Z0L=B09Y;K@jZ4saru6dCp><
zVU79Hqm-i6Y11DL@csXA`EJFRg~QS91}(n0N%BMX0OwefOh4a
zuZ{ZY&JVMm$ut$AqP%(BZqbmtt!x}dI0Os(<500io)+Vgh%eFmf9m${7ASm(Ca9_sujWopgj
z`5Cv9$Xa{Q>-!%4;oxY#Fp3C2jRi%Xg9rj3Ab>)Uw*lD#JSdsa%YuAw>O?u&d)Z_qy<>haI-lg*85czODZG5RFG(T1w5NYdmisI$vV
z^H$N#kv{3e0!r~?{6LbXdvkSJ?Nx&~i7%^tB7&}J%Cbc3nkb4%(gbHMv)Adt7~l7a
zk{HkPkfuS`dJiZJL&m=zFY6MNQp9OOeBiYhqggHX+ZX2?WnLW3dG~OSQWq5D@6;cT
zKoqOx^5hub54aycY&*}ZLn);g+>MBmm~1-7xx*dAyV7=>;oXSMF2HS|-qmWiNn0(-
wqM*ogthQKd@H~$o42j~9POnE0d~RIeAJ(%PeH15L(*OVf07*qoM6N<$g6QE>TL1t6
diff --git a/lms/askbot/skins/common/media/images/dash.gif b/lms/askbot/skins/common/media/images/dash.gif
deleted file mode 100755
index d1ddc507fe00bd654fce38ac8552793aa18c9966..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 44
wcmZ?wbhEHbWMN=rXkcLY|NsBAY10&cvM{hS{AbW%00NK<0~50k11p0y023w&(*OVf
diff --git a/lms/askbot/skins/common/media/images/dialog-warning-off.png b/lms/askbot/skins/common/media/images/dialog-warning-off.png
deleted file mode 100644
index 258e4d86c0d6da8f214fc49eb311fb88814d454e..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 419
zcmV;U0bKrxP)Px#0%A)?L;(MXkIcUS000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igk;0s{aA
zV|5a8|Zt%ng-9WwT
z{<07*xL_f4me>5I8w|POhC{l6HvjvpVmPNqk8>77XZ1TbBEOxeG^C0&(x{>#iHf%W
z05Z1B9t_y9VW0=gl<@_i9W!4?4T2O>5Y$kInb%GPsL04N`-nqe!-gP^J}gs4DnIvo
z*;AKW`rL8NHFxy6WtZ8L_5f(iJ(f7(fqVLFvrV6S9yno%?ond~29hkJ1%W`2K_=>2
zC_|DMh_k(act!_|f(Q`2;RT1CJcf57H@-u>GiK?z_9dUVmXGlC`VB}Lc;@7(Wzqlu
N002ovPDHLkV1fy(s`3B;
diff --git a/lms/askbot/skins/common/media/images/dialog-warning.png b/lms/askbot/skins/common/media/images/dialog-warning.png
deleted file mode 100644
index a9e4ff3991cb0ad2a99cc25e2d13c35e52c680c8..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 603
zcmV-h0;K(kP)J2V5SdEtMnl9PhCxc2Fa&}~V}o=Tx)35=bmuS|@d;cT
zbY;Loz(q+`1+A}82tI&NTm%Y*7F^UoNilyn))L6H_Q1W1%i%j`bA(=?F!-5EVN!MFB_9*XAp~_l%n771CUH65fQk#;c9J-59jBDOQ<*CorDm33|CLV
znm<0~VYP}<3Z)daS`C19yN!q-BK$Z#V4_~XOaDE<2tY)*Zne1F+TuNYlS3Fj19`yz=_l|J
pl>P}02k>Sh;xD3f4Z|KZe*u@?eFz-1;S&G=002ovPDHLkV1mMw_G175
diff --git a/lms/askbot/skins/common/media/images/djangomade124x25_grey.gif b/lms/askbot/skins/common/media/images/djangomade124x25_grey.gif
deleted file mode 100755
index d34bb311615b1378a672a828c7a7916490cd882b..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 2035
zcmcJOSyz(>0zk8Y5oHk(5d(s_FbFhAqNt4L16&YNCyID%iyA#v9i~-V)T#$7C{?Rq
zJuXmNkX;Zk3t36XzAs-E$W8(YK|~TDELE}gFw-9~_w_#9r@MFGo{ViLN+D5@_t6k?
zub_>}Y9`ZKyBS?Q+zuw^CB28p=9m6l=^h>P&(87W>M^fRs?m>(j#(W;9ErkUvGq!o
zR>z=aV9@3qVhSZRzIguQM;cEA4PH>{`)!UPyVLdOr%&oW6O}KX^iRvR21>7B_K!bg
zpuy#NEl_HdeI}XK;2M6dGg;OB7NH7sx`ycjiOOJXqcVp_-!Mh8P9}%M;d7*l9dn^o^Yc8JiXo6#2L{zfi^DzK#p>;|ITX6Se!BxS
zS=nOw+`D&V4qvF$^jqyzo~TDG>y;|lVi{d1<;qn?yF=A))|stRQ2&y_<|)*2U7tjw
zV~V8YUSW?!VYCi#Byx?(%9SZ)x_-xyo5JBc-5#D?H9GEn-bvP4>_a1?Mw^2xQ-LN+
zBe7ec(5U;2FS?oT(KkAyXb
zH`<+SiGsov4vvg6M6xClb-?9rrO;i&BX+lk$mV$`rZ`gN>v8YfiAm6G<101ow4OKP
zUbVqQ?BTjSuXPq%Yd5pISJ261cQQEjo#Yk@y{?1ASAq*s-`cubDGW-lki_O|%{D4etT9^j7JEC5N#je{GL^~hpbMnVVNY!b
z@p%VHZ?SpC-cHTTm>h#mB$`;Qbq9D5VJ`EU_H44c4@Fu%*g{g$2m}
zjre&46atBcB>!Fb`w0kg0fIg2-$QN?-a;-U9a~C|Sx_l#$$
zbyds`pGKe0LOZIh;hTXQH~7?2Jo0
zAKTtHp2*){xDdLxDX)5Bq$d9S)rRYTt~R1BZu8%arq9oL7S0X{cVZ83Md|CWuc5Ww
zgHJrm4n{311;Egli>`(nMilUQIqb4)5Q?cl#}=HzL03S&F5J5ZvF3I&1iAmphxib7
z=~(%y)r?(&s;zbaOiB%VcyqB)a3LW*6`!N2T5$*vtPDAu4Lr&i{1t{hUIbJv$v&)4
zc!CfB!4I<5zFha<24-Hg2z@rDMTRf