CAT-SOOP is a flexible, programmable learning management system based on the Python programming language. https://catsoop.mit.edu
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

210 lines
7.1 KiB

  1. # This file is part of CAT-SOOP
  2. # Copyright (c) 2011-2017 Adam Hartz <hartz@mit.edu>
  3. #
  4. # This program is free software: you can redistribute it and/or modify it under
  5. # the terms of the GNU Affero General Public License as published by the Free
  6. # Software Foundation, either version 3 of the License, or (at your option) any
  7. # later version.
  8. #
  9. # This program is distributed in the hope that it will be useful, but WITHOUT
  10. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  11. # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  12. # details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. import json
  17. import collections.abc
  18. def default_checkbox_checker(x, y):
  19. return float(len([i for i, j in zip(x, y) if i == j])) / len(y)
  20. defaults = {
  21. 'csq_soln': '--',
  22. 'csq_npoints': 1,
  23. 'csq_check_function': lambda x, y: (x == y) * 1.0,
  24. 'csq_checkbox_check_function': default_checkbox_checker,
  25. 'csq_msg_function': lambda sub: '',
  26. 'csq_options': [],
  27. 'csq_show_check': False,
  28. 'csq_multiplechoice_renderer': 'dropdown',
  29. 'csq_multiplechoice_soln_mode': 'value',
  30. }
  31. def total_points(**info):
  32. return info['csq_npoints']
  33. def handle_submission(submissions, **info):
  34. check = info['csq_check_function']
  35. soln = info['csq_soln']
  36. sub = submissions[info['csq_name']]
  37. if info['csq_multiplechoice_renderer'] == 'checkbox':
  38. try:
  39. sub = json.loads(sub)
  40. except:
  41. sub = {}
  42. _sub = []
  43. for ix in range(len(info['csq_options'])):
  44. n = "%s_opt%d" % (info['csq_name'], ix)
  45. _sub.append(sub.get(n, False))
  46. sub = _sub
  47. if check is defaults['csq_check_function']:
  48. check = defaults['csq_checkbox_check_function']
  49. else:
  50. sub = int(sub)
  51. if info['csq_multiplechoice_soln_mode'] == 'value':
  52. sub = info['csq_options'][sub] if sub >= 0 else '--'
  53. check_result = check(sub, soln)
  54. if isinstance(check_result, collections.abc.Mapping):
  55. score = check_result['score']
  56. msg = check_result['msg']
  57. elif isinstance(check_result, collections.abc.Sequence):
  58. score, msg = check_result
  59. else:
  60. score = check_result
  61. mfunc = info['csq_msg_function']
  62. try:
  63. msg = mfunc(sub, soln)
  64. except:
  65. try:
  66. msg = mfunc(sub)
  67. except:
  68. msg = ''
  69. percent = float(score)
  70. if info['csq_show_check']:
  71. if percent == 1.0:
  72. response = '<img src="BASE/images/check.png" />'
  73. elif percent == 0.0:
  74. response = '<img src="BASE/images/cross.png" />'
  75. else:
  76. response = ''
  77. else:
  78. response = ''
  79. response += msg
  80. return {'score': percent, 'msg': response}
  81. def render_html(last_log, **info):
  82. r = info['csq_multiplechoice_renderer']
  83. if r in _renderers:
  84. return _renderers[r](last_log, **info)
  85. else:
  86. return ("<font color='red'>"
  87. "Invalid <tt>multiplechoice</tt> renderer: %s"
  88. "</font>") % r
  89. def render_html_dropdown(last_log, **info):
  90. if last_log is None:
  91. last_log = {}
  92. out = '\n<select name="%s" >' % info['csq_name']
  93. for (ix, i) in enumerate(['--'] + info['csq_options']):
  94. out += '\n<option value="%s" ' % (ix - 1)
  95. if last_log.get(info['csq_name'], '-1') == str(ix - 1):
  96. out += "selected "
  97. out += '>%s</option>' % i
  98. out += '</select>'
  99. return out
  100. def render_html_checkbox(last_log, **info):
  101. if last_log is None:
  102. last_log = {}
  103. out = '<table border="0">'
  104. name = info['csq_name']
  105. last = last_log.get(info['csq_name'], None)
  106. if last is None:
  107. last = {}
  108. else:
  109. try:
  110. last = json.loads(last)
  111. except:
  112. last = {}
  113. if not isinstance(last, dict):
  114. try:
  115. last = {("%s_opt%d" % (name, last)): True}
  116. except:
  117. last = {}
  118. checked = set()
  119. for (ix, i) in enumerate(info['csq_options']):
  120. out += '\n<tr><td align="center">'
  121. _n = "%s_opt%d" % (name, ix)
  122. if last.get(_n, False):
  123. _s = ' checked'
  124. checked.add(_n)
  125. else:
  126. _s = ''
  127. out += '<input type="checkbox" name="%s" value="%s"%s />' % (_n, ix,
  128. _s)
  129. text = csm_language.source_transform_string(info, i)
  130. out += '</td><td>%s</td></tr>' % text
  131. out += '\n</table>'
  132. out += '<input type="hidden" name="%s" id="%s" value="%s">' % (name, name,
  133. last or '')
  134. checked_str = ','.join(('%r: true' % i) for i in checked)
  135. out += (
  136. '\n<script type="text/javascript">'
  137. '\nvar %s_selected = {%s};'
  138. '\n$("#%s").val(JSON.stringify(%s_selected));'
  139. '\n$("input:checkbox[name^=%s_opt]").click(function(){'
  140. '\n %s_selected[$(this).attr("name")] = $(this).prop("checked");'
  141. '\n $("#%s").val(JSON.stringify(%s_selected));});'
  142. '\n</script>') % ((info['csq_name'],
  143. checked_str, ) + (info['csq_name'], ) * 6)
  144. return out
  145. def render_html_radio(last_log, **info):
  146. if last_log is None:
  147. last_log = {}
  148. out = '<table border="0">'
  149. name = info['csq_name']
  150. last = last_log.get(info['csq_name'], None)
  151. for (ix, i) in enumerate(info['csq_options']):
  152. out += '\n<tr><td align="center">'
  153. if last == str(ix):
  154. _s = ' checked'
  155. else:
  156. _s = ''
  157. out += '<input type="radio" name="%s_opts" value="%s"%s />' % (name,
  158. ix, _s)
  159. text = csm_language.source_transform_string(info, i)
  160. out += '</td><td>%s</td></tr>' % text
  161. out += '\n</table>'
  162. out += '<input type="hidden" name="%s" id="%s" value="%s">' % (name, name,
  163. last or '')
  164. out += ('\n<script type="text/javascript">'
  165. '\n$("input:radio[name=%s_opts]").click(function(){'
  166. '\n $("#%s").val($(this).val());});'
  167. '\n</script>') % (info['csq_name'], info['csq_name'])
  168. return out
  169. _renderers = {
  170. 'dropdown': render_html_dropdown,
  171. 'radio': render_html_radio,
  172. 'checkbox': render_html_checkbox,
  173. }
  174. def answer_display(**info):
  175. if info['csq_multiplechoice_renderer'] == 'checkbox':
  176. out = "Solution: <table>"
  177. for c, i in zip(info['csq_soln'], info['csq_options']):
  178. out += '<tr style="height:30px;"></tr>'
  179. out += '<tr><td align="center">'
  180. _im = "check" if c else "cross"
  181. out += '<img src="BASE/images/%s.png" />' % _im
  182. out += '</td><td>'
  183. text = csm_language.source_transform_string(info, i)
  184. out += text
  185. out += '</td></tr>'
  186. out += '</table>'
  187. else:
  188. soln = info['csq_soln']
  189. out = "Solution: %s" % (soln, )
  190. return out