def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) script_name = pl.get_string_attrib(element, 'script-name', None) with open(os.path.join(data['options']['question_path'], script_name)) as f: script = f.read() width = pl.get_string_attrib(element, 'width', '500') height = pl.get_string_attrib(element, 'height', '300') params_names = pl.get_string_attrib(element, 'param-names', None) if params_names is None: client_params = {} else: params_names = params_names.split(sep=',') client_params = {key: data['params'][key] for key in params_names} html_params = { 'script': script, 'width': width, 'height': height, 'client_params': client_params, 'uuid': pl.get_uuid(), } with open('pl-prairiedraw-figure.mustache') as f: html = chevron.render(f, html_params).strip() return html
def render(element_html, data): if data['panel'] != 'question': return '' element = lxml.html.fragment_fromstring(element_html) uuid = pl.get_uuid() raw_file_names = pl.get_string_attrib(element, 'file-names', '') file_names = get_file_names_as_array(raw_file_names) file_names_json = json.dumps(file_names, allow_nan=False) answer_name = get_answer_name(raw_file_names) html_params = {'name': answer_name, 'file_names': file_names_json, 'uuid': uuid} files = data['submitted_answers'].get('_files', None) if files is not None: # Filter out any files not part of this element's file_names filtered_files = [x for x in files if x.get('name', '') in file_names] html_params['has_files'] = True html_params['files'] = json.dumps(filtered_files, allow_nan=False) else: html_params['has_files'] = False with open('pl-file-upload.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() return html
def render(element_html, data): if data['panel'] != 'question': return '' element = lxml.html.fragment_fromstring(element_html) uuid = pl.get_uuid() raw_file_names = pl.get_string_attrib(element, 'file-names', '') file_names = get_file_names_as_array(raw_file_names) file_names_json = json.dumps(file_names, allow_nan=False) answer_name = get_answer_name(raw_file_names) html_params = { 'name': answer_name, 'file_names': file_names_json, 'uuid': uuid } files = data['submitted_answers'].get('_files', None) if files is not None: # Filter out any files not part of this element's file_names filtered_files = [x for x in files if x.get('name', '') in file_names] html_params['has_files'] = True html_params['files'] = json.dumps(filtered_files, allow_nan=False) else: html_params['has_files'] = False with open('pl-file-upload.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() return html
def render(element_html, data): # Grab the name of the element (name of the hidden input tag), and generate a unique UUID # Each element on the page has its own UUID to prevent the JavaScript of other elements from interfering element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') uuid = pl.get_uuid() if data['panel'] == 'question': html_params = { 'question': True, 'number': data['params'][name], 'answers_name': name, 'image_url': data['options']['client_files_element_url'] + '/block_i.png', 'uuid': uuid } elif data['panel'] == 'submission': feedback = data['partial_scores'][name].get('feedback', None) html_params = { 'submission': True, 'submitted': data["raw_submitted_answers"][name], 'feedback': feedback } elif data['panel'] == 'answer': html_params = { 'answer': True, 'correct': data["correct_answers"][name] } with open('clickable-image.mustache', 'r') as f: return chevron.render(f, html_params).strip()
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) file_name = pl.get_string_attrib(element, 'file-name', '') answer_name = get_answer_name(file_name) quill_theme = pl.get_string_attrib(element, 'quill-theme', QUILL_THEME_DEFAULT) placeholder = pl.get_string_attrib(element, 'placeholder', PLACEHOLDER_DEFAULT) uuid = pl.get_uuid() source_file_name = pl.get_string_attrib(element, 'source-file-name', SOURCE_FILE_NAME_DEFAULT) directory = pl.get_string_attrib(element, 'directory', DIRECTORY_DEFAULT) element_text = element_inner_html(element) if data['panel'] == 'question' or data['panel'] == 'submission': html_params = { 'name': answer_name, 'file_name': file_name, 'quill_theme': quill_theme, 'placeholder': placeholder, 'editor_uuid': uuid, 'question': data['panel'] == 'question', 'submission': data['panel'] == 'submission', 'read_only': 'true' if data['panel'] == 'submission' else 'false' } if source_file_name is not None: if directory == 'serverFilesCourse': directory = data['options']['server_files_course_path'] elif directory == 'clientFilesCourse': directory = data['options']['client_files_course_path'] else: directory = os.path.join(data['options']['question_path'], directory) file_path = os.path.join(directory, source_file_name) text_display = open(file_path).read() else: if element_text is not None: text_display = str(element_text) else: text_display = '' html_params['original_file_contents'] = base64.b64encode(text_display.encode('UTF-8').strip()).decode() submitted_file_contents = data['submitted_answers'].get(answer_name, None) if submitted_file_contents: html_params['current_file_contents'] = submitted_file_contents else: html_params['current_file_contents'] = html_params['original_file_contents'] html_params['question'] = data['panel'] == 'question' with open('pl-rich-text-editor.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': html = '' else: raise Exception('Invalid panel type: ' + data['panel']) return html
def render(element_html, element_index, data): if data['panel'] == 'submission': html_params = {'submission': True, 'graded': True, 'uuid': pl.get_uuid()} feedback = data['feedback'] html_params['graded'] = bool(feedback) grading_succeeded = bool(feedback.get('succeeded', None)) html_params['grading_succeeded'] = grading_succeeded if not grading_succeeded: html_params['message'] = feedback.get('message', None) else: results = feedback.get('results', None) if grading_succeeded and results: html_params['succeeded'] = bool(results.get('succeeded', None)) html_params['score'] = format(results.get('score', 0) * 100, '.2f').rstrip('0').rstrip('.') html_params['achieved_max_points'] = (results.get('score', 0) >= 1.0) html_params['results_color'] = '#4CAF50' if (results.get('score', 0) >= 1.0) else '#F44336' html_params['has_message'] = bool(results.get('message', False)) html_params['message'] = results.get('message', None) html_params['has_output'] = bool(results.get('output', False)) html_params['output'] = results.get('output', None) html_params['has_message_or_output'] = bool(html_params['has_message'] or html_params['has_output']) results_tests = results.get('tests', None) html_params['has_tests'] = bool(results.get('tests', None)) if results_tests: html_params['points'] = sum(test['points'] for test in results_tests) html_params['max_points'] = sum(test['max_points'] for test in results_tests) # We need to build a new tests array to massage data a bit tests = [] for index, results_test in enumerate(results_tests): test = {} test['index'] = index test['name'] = results_test.get('name', '') test['has_message'] = bool(results_test.get('message', None)) test['message'] = results_test.get('message', None) test['has_output'] = bool(results_test.get('output', None)) test['output'] = results_test.get('output', None) test['max_points'] = results_test.get('max_points') test['points'] = results_test.get('points') correct = test['max_points'] == test['points'] test['results_color'] = '#4CAF50' if correct else '#F44336' test['results_icon'] = 'glyphicon-ok' if correct else 'glyphicon-remove' test['has_description'] = bool(results_test.get('description', None)) test['description'] = results_test.get('description', None) tests.append(test) html_params['tests'] = tests with open('pl_external_grader_results.mustache', 'r') as f: html = chevron.render(f, html_params).strip() else: html = '' return html
def render(element_html, data): if data['panel'] != 'question': return '' element = lxml.html.fragment_fromstring(element_html) file_name = pl.get_string_attrib(element, 'file-name', '') answer_name = get_answer_name(file_name) editor_config_function = pl.get_string_attrib(element, 'editor-config-function', None) ace_mode = pl.get_string_attrib(element, 'ace-mode', None) ace_theme = pl.get_string_attrib(element, 'ace-theme', None) uuid = pl.get_uuid() source_file_name = pl.get_string_attrib(element, 'source-file-name', None) html_params = { 'name': answer_name, 'file_name': file_name, 'ace_mode': ace_mode, 'ace_theme': ace_theme, 'editor_config_function': editor_config_function, 'uuid': uuid } if source_file_name is not None: file_path = os.path.join(data['options']['question_path'], source_file_name) text_display = open(file_path).read() else: if element.text is not None: text_display = str(element.text) else: text_display = '' html_params['original_file_contents'] = base64.b64encode( text_display.encode('UTF-8').strip()).decode() submitted_file_contents = data['submitted_answers'].get(answer_name, None) if submitted_file_contents: html_params['current_file_contents'] = submitted_file_contents else: html_params['current_file_contents'] = html_params[ 'original_file_contents'] if data['panel'] == 'question': html_params['question'] = True with open('pl-file-editor.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' return html
def render(element_html, element_index, data): element = lxml.html.fragment_fromstring(element_html) digits = pl.get_integer_attrib(element, 'digits', 2) matlab_data = '' python_data = 'import numpy as np\n\n' for child in element: if child.tag == 'variable': # Raise exception of variable does not have a name pl.check_attribs(child, required_attribs=['params_name'], optional_attribs=[]) # Get name of variable var_name = pl.get_string_attrib(child, 'params_name') # Get value of variable, raising exception if variable does not exist var_data = data['params'].get(var_name, None) if var_data is None: raise Exception('No value in data["params"] for variable %s in pl_matrix_output element' % var_name) # If the variable is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) var_data = pl.from_json(var_data) if np.isscalar(var_data): prefix = '' suffix = '' else: # Wrap the variable in an ndarray (if it's already one, this does nothing) var_data = np.array(var_data) # Check shape of variable if var_data.ndim != 2: raise Exception('Value in data["params"] for variable %s in pl_matrix_output element must be a scalar or a 2D array' % var_name) # Create prefix/suffix so python string is np.array( ... ) prefix = 'np.array(' suffix = ')' # Create string for matlab and python format matlab_data += pl.inner_html(child) + ' = ' + pl.string_from_2darray(var_data, language='matlab', digits=digits) + ';\n' python_data += pl.inner_html(child) + ' = ' + prefix + pl.string_from_2darray(var_data, language='python', digits=digits) + suffix + '\n' html_params = { 'default_is_matlab': True, 'matlab_data': matlab_data, 'python_data': python_data, 'element_index': element_index, 'uuid': pl.get_uuid() } with open('pl_matrix_output.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() return html
def render(element_html, data): if data['panel'] != 'question': return '' element = lxml.html.fragment_fromstring(element_html) file_name = pl.get_string_attrib(element, 'file-name', '') answer_name = get_answer_name(file_name) editor_config_function = pl.get_string_attrib(element, 'editor-config-function', None) ace_mode = pl.get_string_attrib(element, 'ace-mode', None) ace_theme = pl.get_string_attrib(element, 'ace-theme', None) uuid = pl.get_uuid() source_file_name = pl.get_string_attrib(element, 'source-file-name', None) html_params = { 'name': answer_name, 'file_name': file_name, 'ace_mode': ace_mode, 'ace_theme': ace_theme, 'editor_config_function': editor_config_function, 'uuid': uuid } if source_file_name is not None: file_path = os.path.join(data['options']['question_path'], source_file_name) text_display = open(file_path).read() else: if element.text is not None: text_display = str(element.text) else: text_display = '' html_params['original_file_contents'] = base64.b64encode(text_display.encode('UTF-8').strip()).decode() submitted_file_contents = data['submitted_answers'].get(answer_name, None) if submitted_file_contents: html_params['current_file_contents'] = submitted_file_contents else: html_params['current_file_contents'] = html_params['original_file_contents'] if data['panel'] == 'question': html_params['question'] = True with open('pl-file-editor.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) engine = pl.get_string_attrib(element, 'engine', 'dot') # Read the contents of this element as the data to render # we dump the string to json to ensure that newlines are # properly encoded graphviz_data = json.dumps(str(element.text)) html_params = { 'uuid': pl.get_uuid(), 'workerURL': '/node_modules/viz.js/full.render.js', 'data': graphviz_data, 'engine': engine, } with open('pl-graphviz-render.mustache') as f: html = chevron.render(f, html_params).strip() return html
def render(element_html, data): # Get attribs element = lxml.html.fragment_fromstring(element_html) engine = pl.get_string_attrib(element, 'engine', ENGINE_DEFAULT) input_param = pl.get_string_attrib(element, 'params-name-matrix', PARAMS_NAME_MATRIX_DEFAULT) input_label = pl.get_string_attrib(element, 'params-name-labels', PARAMS_NAME_LABELS_DEFAULT) if len(str(element.text)) == 0 and input_param is None: raise Exception( 'No graph source given! Must either define graph in HTML or provide source in params.' ) graphviz_data = None if input_param is not None: mat = np.array(pl.from_json(data['params'][input_param])) label = None if input_label is not None: label = np.array(pl.from_json(data['params'][input_label])) graphviz_data = json.dumps( graphviz_from_matrix(mat, label, engine, element)) else: # Read the contents of this element as the data to render # we dump the string to json to ensure that newlines are # properly encoded graphviz_data = json.dumps(str(element.text)) html_params = { 'uuid': pl.get_uuid(), 'workerURL': '/node_modules/viz.js/full.render.js', 'data': graphviz_data, 'engine': engine, } with open('pl-graph.mustache') as f: html = chevron.render(f, html_params).strip() return html
def render(element_html, element_index, data): if data['panel'] != 'question': return '' element = lxml.html.fragment_fromstring(element_html) file_name = pl.get_string_attrib(element, 'file_name', '') answer_name = get_answer_name(file_name) editor_config_function = pl.get_string_attrib(element, 'editor_config_function', None) ace_mode = pl.get_string_attrib(element, 'ace_mode', None) ace_theme = pl.get_string_attrib(element, 'ace_theme', None) uuid = pl.get_uuid() html_params = { 'name': answer_name, 'file_name': file_name, 'ace_mode': ace_mode, 'ace_theme': ace_theme, 'editor_config_function': editor_config_function, 'uuid': uuid } html_params['original_file_contents'] = base64.b64encode( str(element.text).encode('UTF-8').strip() or '').decode() submitted_file_contents = data['submitted_answers'].get(answer_name, None) if submitted_file_contents: html_params['current_file_contents'] = submitted_file_contents else: html_params['current_file_contents'] = html_params[ 'original_file_contents'] if data['panel'] == 'question': html_params['question'] = True with open('pl_file_editor.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' return html
def render(element_html, data): if data['panel'] != 'submission': return '' html_params = {'uuid': pl.get_uuid()} # Fetch the list of required files for this question required_file_names = data['params'].get('_required_file_names', []) html_params['required_files'] = required_file_names # Fetch any submitted files submitted_files = data['submitted_answers'].get('_files', []) # Pass through format errors from the file input elements html_params['errors'] = data['format_errors'].get('_files', []) # Decode and reshape files into a useful form if len(submitted_files) > 0: files = [] for idx, file in enumerate(submitted_files): b64contents = file['contents'] or '' try: contents = base64.b64decode(b64contents).decode() except UnicodeDecodeError: contents = 'Unable to decode file.' files.append({ 'name': file['name'], 'contents': contents, 'contentsb64': b64contents, 'index': idx }) html_params['has_files'] = True html_params['files'] = files else: html_params['has_files'] = False with open('pl-file-preview.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() return html
def render(element_html, data): # Load all extensions extensions = pl.load_all_extensions(data) for extension in extensions.values(): backends = extension.backends for name, backend in backends.items(): matrix_backends[name] = backend # Get attribs element = lxml.html.fragment_fromstring(element_html) engine = pl.get_string_attrib(element, 'engine', ENGINE_DEFAULT) input_param = pl.get_string_attrib(element, 'params-name-matrix', PARAMS_NAME_MATRIX_DEFAULT) input_type = pl.get_string_attrib(element, 'params-type', PARAMS_TYPE_DEFAULT) if len(str(element.text)) == 0 and input_param is None: raise Exception('No graph source given! Must either define graph in HTML or provide source in params.') if input_param is not None: if input_type in matrix_backends: graphviz_data = json.dumps(matrix_backends[input_type](element, data)) else: raise Exception(f'Unknown graph type "{input_type}".') else: # Read the contents of this element as the data to render # we dump the string to json to ensure that newlines are # properly encoded graphviz_data = json.dumps(str(element.text)) html_params = { 'uuid': pl.get_uuid(), 'workerURL': '/node_modules/viz.js/full.render.js', 'data': graphviz_data, 'engine': engine, } with open('pl-graph.mustache') as f: html = chevron.render(f, html_params).strip() return html
def render(element_html, data): if data['panel'] != 'submission': return '' html_params = {'uuid': pl.get_uuid()} # Fetch the list of required files for this question required_file_names = data['params'].get('_required_file_names', []) html_params['required_files'] = required_file_names # Fetch any submitted files submitted_files = data['submitted_answers'].get('_files', []) # Pass through format errors from the file input elements html_params['errors'] = data['format_errors'].get('_files', []) # Decode and reshape files into a useful form if len(submitted_files) > 0: files = [] for idx, file in enumerate(submitted_files): try: contents = base64.b64decode(file['contents'] or '').decode() except UnicodeDecodeError: contents = 'Unable to decode file.' files.append({ 'name': file['name'], 'contents': contents, 'index': idx }) html_params['has_files'] = True html_params['files'] = files else: html_params['has_files'] = False with open('pl-file-preview.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') label = pl.get_string_attrib(element, 'label', None) variables_string = pl.get_string_attrib(element, 'variables', None) variables = get_variables_list(variables_string) display = pl.get_string_attrib(element, 'display', 'inline') allow_complex = pl.get_boolean_attrib(element, 'allow-complex', False) imaginary_unit = pl.get_string_attrib(element, 'imaginary-unit-for-display', 'i') if data['panel'] == 'question': editable = data['editable'] raw_submitted_answer = data['raw_submitted_answers'].get(name, None) operators = ', '.join(['cos', 'sin', 'tan', 'exp', 'log', 'sqrt', '( )', '+', '-', '*', '/', '^', '**']) constants = ', '.join(['pi, e']) info_params = { 'format': True, 'variables': variables_string, 'operators': operators, 'constants': constants, 'allow_complex': allow_complex, } with open('pl-symbolic-input.mustache', 'r', encoding='utf-8') as f: info = chevron.render(f, info_params).strip() with open('pl-symbolic-input.mustache', 'r', encoding='utf-8') as f: info_params.pop('format', None) info_params['shortformat'] = True shortinfo = chevron.render(f, info_params).strip() html_params = { 'question': True, 'name': name, 'label': label, 'editable': editable, 'info': info, 'shortinfo': shortinfo, 'uuid': pl.get_uuid(), 'allow_complex': allow_complex, } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) if display == 'inline': html_params['inline'] = True elif display == 'block': html_params['block'] = True else: raise ValueError('method of display "%s" is not valid (must be "inline" or "block")' % display) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) with open('pl-symbolic-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'label': label, 'parse_error': parse_error, 'uuid': pl.get_uuid() } if parse_error is None: a_sub = data['submitted_answers'][name] if isinstance(a_sub, str): # this is for backward-compatibility a_sub = phs.convert_string_to_sympy(a_sub, variables, allow_complex=allow_complex) else: a_sub = phs.json_to_sympy(a_sub, allow_complex=allow_complex) a_sub = a_sub.subs(sympy.I, sympy.Symbol(imaginary_unit)) html_params['a_sub'] = sympy.latex(a_sub) else: raw_submitted_answer = data['raw_submitted_answers'].get(name, None) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) if display == 'inline': html_params['inline'] = True elif display == 'block': html_params['block'] = True else: raise ValueError('method of display "%s" is not valid (must be "inline" or "block")' % display) with open('pl-symbolic-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': a_tru = data['correct_answers'].get(name, None) if a_tru is not None: if isinstance(a_tru, str): # this is so instructors can specify the true answer simply as a string a_tru = phs.convert_string_to_sympy(a_tru, variables, allow_complex=allow_complex) else: a_tru = phs.json_to_sympy(a_tru, allow_complex=allow_complex) a_tru = a_tru.subs(sympy.I, sympy.Symbol(imaginary_unit)) html_params = { 'answer': True, 'label': label, 'a_tru': sympy.latex(a_tru) } with open('pl-symbolic-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') answers = data['params'].get(name, []) inline = pl.get_boolean_attrib(element, 'inline', INLINE_DEFAULT) submitted_key = data['submitted_answers'].get(name, None) correct_key = data['correct_answers'].get(name, {'key': None}).get('key', None) if data['panel'] == 'question': editable = data['editable'] partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) display_score = (score is not None) feedback = partial_score.get('feedback', None) # Set up the templating for each answer answerset = [] for answer in answers: answer_html = { 'key': answer['key'], 'checked': (submitted_key == answer['key']), 'html': answer['html'], 'display_score_badge': display_score and submitted_key == answer['key'], 'display_feedback': submitted_key == answer['key'] and feedback is not None, 'feedback': feedback } if answer_html['display_score_badge']: answer_html['correct'] = (correct_key == answer['key']) answer_html['incorrect'] = (correct_key != answer['key']) answerset.append(answer_html) html_params = { 'question': True, 'inline': inline, 'name': name, 'editable': editable, 'display_score_badge': display_score, 'answers': answerset, 'hide_letter_keys': pl.get_boolean_attrib(element, 'hide-letter-keys', HIDE_LETTER_KEYS_DEFAULT) } # Display the score badge if necessary if display_score: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl-multiple-choice.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'parse_error': parse_error, 'uuid': pl.get_uuid(), 'hide_letter_keys': pl.get_boolean_attrib(element, 'hide-letter-keys', HIDE_LETTER_KEYS_DEFAULT) } if parse_error is None: submitted_answer = next(filter(lambda a: a['key'] == submitted_key, answers), None) html_params['key'] = submitted_key html_params['answer'] = submitted_answer partial_score = data['partial_scores'].get(name, {'score': None}) feedback = partial_score.get('feedback', None) score = partial_score.get('score', None) if score is not None: html_params['display_score_badge'] = True try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) html_params['display_feedback'] = feedback is not None html_params['feedback'] = feedback with open('pl-multiple-choice.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': correct_answer = data['correct_answers'].get(name, None) if correct_answer is None: raise ValueError('No true answer.') else: html_params = { 'answer': True, 'answers': correct_answer, 'key': correct_answer['key'], 'html': correct_answer['html'], 'inline': inline, 'hide_letter_keys': pl.get_boolean_attrib(element, 'hide-letter-keys', HIDE_LETTER_KEYS_DEFAULT) } with open('pl-multiple-choice.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def render(element_html, element_index, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers_name') label = pl.get_string_attrib(element, 'label', None) suffix = pl.get_string_attrib(element, 'suffix', None) display = pl.get_string_attrib(element, 'display', 'inline') if data['panel'] == 'question': editable = data['editable'] raw_submitted_answer = data['raw_submitted_answers'].get(name, None) # Get comparison parameters and info strings comparison = pl.get_string_attrib(element, 'comparison', 'relabs') if comparison == 'relabs': rtol = pl.get_float_attrib(element, 'rtol', 1e-2) atol = pl.get_float_attrib(element, 'atol', 1e-8) info_params = { 'format': True, 'relabs': True, 'rtol': rtol, 'atol': atol } elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) info_params = { 'format': True, 'sigfig': True, 'digits': digits, 'comparison_eps': 0.51 * (10**-(digits - 1)) } elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) info_params = { 'format': True, 'decdig': True, 'digits': digits, 'comparison_eps': 0.51 * (10**-(digits - 0)) } else: raise ValueError( 'method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) info_params['allow_complex'] = pl.get_boolean_attrib( element, 'allow_complex', False) with open('pl_number_input.mustache', 'r', encoding='utf-8') as f: info = chevron.render(f, info_params).strip() with open('pl_number_input.mustache', 'r', encoding='utf-8') as f: info_params.pop('format', None) info_params['shortformat'] = True shortinfo = chevron.render(f, info_params).strip() html_params = { 'question': True, 'name': name, 'label': label, 'suffix': suffix, 'editable': editable, 'info': info, 'shortinfo': shortinfo, 'uuid': pl.get_uuid() } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) if display == 'inline': html_params['inline'] = True elif display == 'block': html_params['block'] = True else: raise ValueError( 'method of display "%s" is not valid (must be "inline" or "block")' % display) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) with open('pl_number_input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'label': label, 'parse_error': parse_error, 'uuid': pl.get_uuid() } if parse_error is None: # Get submitted answer, raising an exception if it does not exist a_sub = data['submitted_answers'].get(name, None) if a_sub is None: raise Exception('submitted answer is None') # If answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_sub = pl.from_json(a_sub) html_params['suffix'] = suffix html_params['a_sub'] = '{:.12g}'.format(a_sub) else: raw_submitted_answer = data['raw_submitted_answers'].get( name, None) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape( raw_submitted_answer) partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl_number_input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is not None: # Get comparison parameters comparison = pl.get_string_attrib(element, 'comparison', 'relabs') if comparison == 'relabs': rtol = pl.get_float_attrib(element, 'rtol', 1e-2) atol = pl.get_float_attrib(element, 'atol', 1e-8) # FIXME: render correctly with respect to rtol and atol a_tru = '{:.12g}'.format(a_tru) elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) a_tru = pl.string_from_number_sigfig(a_tru, digits=digits) elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) a_tru = '{:.{ndigits}f}'.format(a_tru, ndigits=digits) else: raise ValueError( 'method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) # FIXME: render correctly with respect to method of comparison html_params = { 'answer': True, 'label': label, 'a_tru': a_tru, 'suffix': suffix } with open('pl_number_input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def prepare(element_html, data): element = lxml.html.fragment_fromstring(element_html) answer_name = pl.get_string_attrib(element, 'answers-name') required_attribs = ['answers-name'] optional_attribs = [ 'source-blocks-order', 'grading-method', 'indentation', 'source-header', 'solution-header', 'file-name', 'solution-placement', 'max-incorrect', 'min-incorrect', 'weight', 'inline', 'max-indent', 'feedback' ] pl.check_attribs(element, required_attribs=required_attribs, optional_attribs=optional_attribs) check_indentation = pl.get_boolean_attrib(element, 'indentation', INDENTION_DEFAULT) grading_method = pl.get_string_attrib(element, 'grading-method', GRADING_METHOD_DEFAULT) feedback_type = pl.get_string_attrib(element, 'feedback', FEEDBACK_DEFAULT) accepted_grading_method = [ 'ordered', 'unordered', 'ranking', 'dag', 'external' ] if grading_method not in accepted_grading_method: raise Exception( 'The grading-method attribute must be one of the following: ' + accepted_grading_method) if (grading_method != 'dag' and feedback_type != 'none') or \ (grading_method == 'dag' and feedback_type not in ['none', 'first-wrong']): raise Exception('feedback type "' + feedback_type + '" is not available with the "' + grading_method + '" grading-method.') correct_answers = [] incorrect_answers = [] def prepare_tag(html_tags, index, group=None): if html_tags.tag != 'pl-answer': raise Exception( 'Any html tags nested inside <pl-order-blocks> must be <pl-answer> or <pl-block-group>. \ Any html tags nested inside <pl-block-group> must be <pl-answer>' ) if grading_method == 'external': pl.check_attribs(html_tags, required_attribs=[], optional_attribs=['correct']) elif grading_method == 'unordered': pl.check_attribs(html_tags, required_attribs=[], optional_attribs=['correct', 'indent']) elif grading_method in ['ranking', 'ordered']: pl.check_attribs(html_tags, required_attribs=[], optional_attribs=['correct', 'ranking', 'indent']) elif grading_method == 'dag': pl.check_attribs(html_tags, required_attribs=[], optional_attribs=['correct', 'tag', 'depends']) is_correct = pl.get_boolean_attrib(html_tags, 'correct', PL_ANSWER_CORRECT_DEFAULT) answer_indent = pl.get_integer_attrib(html_tags, 'indent', None) inner_html = pl.inner_html(html_tags) ranking = pl.get_integer_attrib(html_tags, 'ranking', -1) tag = pl.get_string_attrib(html_tags, 'tag', None) depends = pl.get_string_attrib(html_tags, 'depends', '') depends = depends.strip().split(',') if depends else [] if check_indentation is False and answer_indent is not None: raise Exception( '<pl-answer> should not specify indentation if indentation is disabled.' ) answer_data_dict = { 'inner_html': inner_html, 'indent': answer_indent, 'ranking': ranking, 'index': index, 'tag': tag, # only used with DAG grader 'depends': depends, # only used with DAG grader 'group': group # only used with DAG grader } if is_correct: correct_answers.append(answer_data_dict) else: incorrect_answers.append(answer_data_dict) index = 0 group_counter = 0 for html_tags in element: # iterate through the html tags inside pl-order-blocks if html_tags.tag is lxml.etree.Comment: continue elif html_tags.tag == 'pl-block-group': if grading_method != 'dag': raise Exception( 'Block groups only supported in the "dag" grading mode.') group_counter += 1 for grouped_tag in html_tags: if html_tags.tag is lxml.etree.Comment: continue else: prepare_tag(grouped_tag, index, group_counter) index += 1 else: prepare_tag(html_tags, index) index += 1 if pl.get_string_attrib(element, 'grading-method', GRADING_METHOD_DEFAULT ) != 'external' and len(correct_answers) == 0: raise Exception( 'There are no correct answers specified for this question.') all_incorrect_answers = len(incorrect_answers) max_incorrect = pl.get_integer_attrib(element, 'max-incorrect', all_incorrect_answers) min_incorrect = pl.get_integer_attrib(element, 'min-incorrect', all_incorrect_answers) if min_incorrect > len(incorrect_answers) or max_incorrect > len( incorrect_answers): raise Exception( 'The min-incorrect or max-incorrect attribute may not exceed the number of incorrect <pl-answers>.' ) if min_incorrect > max_incorrect: raise Exception( 'The attribute min-incorrect must be smaller than max-incorrect.') incorrect_answers_count = random.randint(min_incorrect, max_incorrect) sampled_correct_answers = correct_answers sampled_incorrect_answers = random.sample(incorrect_answers, incorrect_answers_count) mcq_options = sampled_correct_answers + sampled_incorrect_answers source_blocks_order = pl.get_string_attrib(element, 'source-blocks-order', SOURCE_BLOCKS_ORDER_DEFAULT) if source_blocks_order == 'random': random.shuffle(mcq_options) elif source_blocks_order == 'ordered': mcq_options.sort(key=lambda a: a['index']) else: raise Exception( 'The specified option for the "source-blocks-order" attribute is invalid.' ) # data['params'][answer_name] = filter_keys_from_array(mcq_options, 'inner_html') for option in mcq_options: option['uuid'] = pl.get_uuid() data['params'][answer_name] = mcq_options data['correct_answers'][answer_name] = correct_answers
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') partial_credit = pl.get_boolean_attrib(element, 'partial-credit', PARTIAL_CREDIT_DEFAULT) partial_credit_method = pl.get_string_attrib(element, 'partial-credit-method', PARTIAL_CREDIT_METHOD_DEFAULT) hide_score_badge = pl.get_boolean_attrib(element, 'hide-score-badge', HIDE_SCORE_BADGE_DEFAULT) editable = data['editable'] # answer feedback is not displayed when partial credit is True # (unless the question is disabled) show_answer_feedback = True if (partial_credit and editable) or hide_score_badge: show_answer_feedback = False display_answers = data['params'].get(name, []) inline = pl.get_boolean_attrib(element, 'inline', INLINE_DEFAULT) submitted_keys = data['submitted_answers'].get(name, []) # if there is only one key then it is passed as a string, # not as a length-one list, so we fix that next if isinstance(submitted_keys, str): submitted_keys = [submitted_keys] correct_answer_list = data['correct_answers'].get(name, []) correct_keys = [answer['key'] for answer in correct_answer_list] if data['panel'] == 'question': partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) answerset = [] for answer in display_answers: answer_html = { 'key': answer['key'], 'checked': (answer['key'] in submitted_keys), 'html': answer['html'].strip(), 'display_score_badge': score is not None and show_answer_feedback and answer['key'] in submitted_keys } if answer_html['display_score_badge']: answer_html['correct'] = (answer['key'] in correct_keys) answer_html['incorrect'] = (answer['key'] not in correct_keys) answerset.append(answer_html) info_params = {'format': True} # Adds decorative help text per bootstrap formatting guidelines: # http://getbootstrap.com/docs/4.0/components/forms/#help-text # Determine whether we should add a choice selection requirement hide_help_text = pl.get_boolean_attrib(element, 'hide-help-text', HIDE_HELP_TEXT_DEFAULT) if not hide_help_text: # Should we reveal the depth of the choice? detailed_help_text = pl.get_boolean_attrib(element, 'detailed-help-text', DETAILED_HELP_TEXT_DEFAULT) min_correct = pl.get_integer_attrib(element, 'min-correct', 1) max_correct = pl.get_integer_attrib(element, 'max-correct', len(correct_answer_list)) if detailed_help_text: if min_correct != max_correct: insert_text = ' between <b>%d</b> and <b>%d</b> options.' % (min_correct, max_correct) helptext = '<small class="form-text text-muted">Select ' + insert_text + '</small>' else: insert_text = ' exactly <b>%d</b> options.' % (max_correct) helptext = '<small class="form-text text-muted">Select' + insert_text + '</small>' else: insert_text = ' at least one option.' helptext = '<small class="form-text text-muted">Select all possible options that apply.</small>' if partial_credit: if partial_credit_method == 'PC': gradingtext = 'You must select ' + insert_text + ' You will receive a score of <code>100% * (t - f) / n</code>, ' \ + 'where <code>t</code> is the number of true options that you select, <code>f</code> ' \ + 'is the number of false options that you select, and <code>n</code> is the total number of true options. ' \ + 'At minimum, you will receive a score of 0%.' else: gradingtext = 'You must select ' + insert_text + ' You will receive a score of <code>100% * (t + f) / ' + str(len(display_answers)) + '</code>, ' \ + 'where <code>t</code> is the number of true options that you select and <code>f</code> ' \ + 'is the number of false options that you do not select.' else: gradingtext = 'You must select' + insert_text + ' You will receive a score of 100% ' \ + 'if you select all options that are true and no options that are false. ' \ + 'Otherwise, you will receive a score of 0%.' info_params.update({'gradingtext': gradingtext}) with open('pl-checkbox.mustache', 'r', encoding='utf-8') as f: info = chevron.render(f, info_params).strip() html_params = { 'question': True, 'name': name, 'editable': editable, 'uuid': pl.get_uuid(), 'info': info, 'answers': answerset, 'inline': inline, 'hide_letter_keys': pl.get_boolean_attrib(element, 'hide-letter-keys', HIDE_LETTER_KEYS_DEFAULT) } if not hide_help_text: html_params['helptext'] = helptext if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl-checkbox.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) if parse_error is None: partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) answers = [] for submitted_key in submitted_keys: submitted_answer = next(filter(lambda a: a['key'] == submitted_key, display_answers), None) answer_item = { 'key': submitted_key, 'html': submitted_answer['html'], 'display_score_badge': score is not None and show_answer_feedback } if answer_item['display_score_badge']: answer_item['correct'] = (submitted_key in correct_keys) answer_item['incorrect'] = (submitted_key not in correct_keys) answers.append(answer_item) html_params = { 'submission': True, 'display_score_badge': (score is not None), 'answers': answers, 'inline': inline, 'hide_letter_keys': pl.get_boolean_attrib(element, 'hide-letter-keys', HIDE_LETTER_KEYS_DEFAULT) } if html_params['display_score_badge']: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl-checkbox.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html_params = { 'submission': True, 'uuid': pl.get_uuid(), 'parse_error': parse_error, 'inline': inline, } with open('pl-checkbox.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': if not pl.get_boolean_attrib(element, 'hide-answer-panel', HIDE_ANSWER_PANEL_DEFAULT): correct_answer_list = data['correct_answers'].get(name, []) if len(correct_answer_list) == 0: raise ValueError('At least one option must be true.') else: html_params = { 'answer': True, 'inline': inline, 'answers': correct_answer_list, 'hide_letter_keys': pl.get_boolean_attrib(element, 'hide-letter-keys', HIDE_LETTER_KEYS_DEFAULT) } with open('pl-checkbox.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise ValueError('Invalid panel type: %s' % data['panel']) return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) # get the name of the element, in this case, the name of the array name = pl.get_string_attrib(element, 'answers-name') label = pl.get_string_attrib(element, 'label', None) allow_partial_credit = pl.get_boolean_attrib(element, 'allow-partial-credit', False) allow_feedback = pl.get_boolean_attrib(element, 'allow-feedback', allow_partial_credit) if data['panel'] == 'question': editable = data['editable'] # Get true answer a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is None: raise Exception('No value in data["correct_answers"] for variable %s in pl-matrix-component-input element' % name) else: if np.isscalar(a_tru): raise Exception('Value in data["correct_answers"] for variable %s in pl-matrix-component-input element cannot be a scalar.' % name) else: a_tru = np.array(a_tru) if a_tru.ndim != 2: raise Exception('Value in data["correct_answers"] for variable %s in pl-matrix-component-input element must be a 2D array.' % name) else: m, n = np.shape(a_tru) input_array = createTableForHTMLDisplay(m, n, name, label, data, 'input') # Get comparison parameters and info strings comparison = pl.get_string_attrib(element, 'comparison', 'relabs') if comparison == 'relabs': rtol = pl.get_float_attrib(element, 'rtol', 1e-2) atol = pl.get_float_attrib(element, 'atol', 1e-8) if (rtol < 0): raise ValueError('Attribute rtol = {:g} must be non-negative'.format(rtol)) if (atol < 0): raise ValueError('Attribute atol = {:g} must be non-negative'.format(atol)) info_params = {'format': True, 'relabs': True, 'rtol': '{:g}'.format(rtol), 'atol': '{:g}'.format(atol)} elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) if (digits < 0): raise ValueError('Attribute digits = {:d} must be non-negative'.format(digits)) info_params = {'format': True, 'sigfig': True, 'digits': '{:d}'.format(digits), 'comparison_eps': 0.51 * (10**-(digits - 1))} elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) if (digits < 0): raise ValueError('Attribute digits = {:d} must be non-negative'.format(digits)) info_params = {'format': True, 'decdig': True, 'digits': '{:d}'.format(digits), 'comparison_eps': 0.51 * (10**-(digits - 0))} else: raise ValueError('method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: info = chevron.render(f, info_params).strip() with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: info_params.pop('format', None) info_params['shortformat'] = True shortinfo = chevron.render(f, info_params).strip() html_params = { 'question': True, 'name': name, 'label': label, 'editable': editable, 'info': info, 'shortinfo': shortinfo, 'input_array': input_array, 'inline': True, 'uuid': pl.get_uuid() } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'label': label, 'parse_error': parse_error, 'uuid': pl.get_uuid() } a_tru = pl.from_json(data['correct_answers'].get(name, None)) m, n = np.shape(a_tru) partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) if parse_error is None: # Get submitted answer, raising an exception if it does not exist a_sub = data['submitted_answers'].get(name, None) if a_sub is None: raise Exception('submitted answer is None') # If answer is in a format generated by pl.to_json, convert it back to a standard type (otherwise, do nothing) a_sub = pl.from_json(a_sub) # Wrap answer in an ndarray (if it's already one, this does nothing) a_sub = np.array(a_sub) # Format submitted answer as a latex string sub_latex = '$' + pl.latex_from_2darray(a_sub, presentation_type='g', digits=12) + '$' # When allowing feedback, display submitted answers using html table sub_html_table = createTableForHTMLDisplay(m, n, name, label, data, 'output-feedback') if allow_feedback and score is not None: if score < 1: html_params['a_sub_feedback'] = sub_html_table else: html_params['a_sub'] = sub_latex else: html_params['a_sub'] = sub_latex else: # create html table to show submitted answer when there is an invalid format html_params['raw_submitted_answer'] = createTableForHTMLDisplay(m, n, name, label, data, 'output-invalid') with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': # Get true answer - do nothing if it does not exist a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is not None: a_tru = np.array(a_tru) # Get comparison parameters and create the display data comparison = pl.get_string_attrib(element, 'comparison', 'relabs') if comparison == 'relabs': rtol = pl.get_float_attrib(element, 'rtol', 1e-2) atol = pl.get_float_attrib(element, 'atol', 1e-8) # FIXME: render correctly with respect to rtol and atol latex_data = '$' + pl.latex_from_2darray(a_tru, presentation_type='g', digits=12) + '$' elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) latex_data = '$' + pl.latex_from_2darray(a_tru, presentation_type='sigfig', digits=digits) + '$' elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) latex_data = '$' + pl.latex_from_2darray(a_tru, presentation_type='f', digits=digits) + '$' else: raise ValueError('method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) html_params = { 'answer': True, 'label': label, 'latex_data': latex_data, 'uuid': pl.get_uuid() } with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') label = pl.get_string_attrib(element, 'label', LABEL_DEFAULT) variables_string = pl.get_string_attrib(element, 'variables', VARIABLES_DEFAULT) variables = get_variables_list(variables_string) display = pl.get_string_attrib(element, 'display', DISPLAY_DEFAULT) allow_complex = pl.get_boolean_attrib(element, 'allow-complex', ALLOW_COMPLEX_DEFAULT) imaginary_unit = pl.get_string_attrib(element, 'imaginary-unit-for-display', IMAGINARY_UNIT_FOR_DISPLAY_DEFAULT) size = pl.get_integer_attrib(element, 'size', SIZE_DEFAULT) operators = [ 'cos', 'sin', 'tan', 'exp', 'log', 'sqrt', '( )', '+', '-', '*', '/', '^', '**' ] constants = ['pi', 'e'] if data['panel'] == 'question': editable = data['editable'] raw_submitted_answer = data['raw_submitted_answers'].get(name, None) info_params = { 'format': True, 'variables': variables, 'operators': operators, 'constants': constants, 'allow_complex': allow_complex, } with open('pl-symbolic-input.mustache', 'r', encoding='utf-8') as f: info = chevron.render(f, info_params).strip() with open('pl-symbolic-input.mustache', 'r', encoding='utf-8') as f: info_params.pop('format', None) info_params['shortformat'] = True shortinfo = chevron.render(f, info_params).strip() html_params = { 'question': True, 'name': name, 'label': label, 'editable': editable, 'info': info, 'shortinfo': shortinfo, 'size': size, 'show_info': pl.get_boolean_attrib(element, 'show-help-text', SHOW_HELP_TEXT_DEFAULT), 'uuid': pl.get_uuid(), 'allow_complex': allow_complex, 'show_placeholder': size >= PLACEHOLDER_TEXT_THRESHOLD } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) if display == 'inline': html_params['inline'] = True elif display == 'block': html_params['block'] = True else: raise ValueError( 'method of display "%s" is not valid (must be "inline" or "block")' % display) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) with open('pl-symbolic-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'label': label, 'parse_error': parse_error, 'uuid': pl.get_uuid() } if parse_error is None and name in data['submitted_answers']: a_sub = data['submitted_answers'][name] if isinstance(a_sub, str): # this is for backward-compatibility a_sub = phs.convert_string_to_sympy( a_sub, variables, allow_complex=allow_complex) else: a_sub = phs.json_to_sympy(a_sub, allow_complex=allow_complex) a_sub = a_sub.subs(sympy.I, sympy.Symbol(imaginary_unit)) html_params['a_sub'] = sympy.latex(a_sub) elif name not in data['submitted_answers']: html_params['missing_input'] = True html_params['parse_error'] = None else: # Use the existing format text in the invalid popup. info_params = { 'format': True, 'variables': variables, 'operators': operators, 'constants': constants, 'allow_complex': allow_complex, } with open('pl-symbolic-input.mustache', 'r', encoding='utf-8') as f: info = chevron.render(f, info_params).strip() # Render invalid popup raw_submitted_answer = data['raw_submitted_answers'].get( name, None) with open('pl-symbolic-input.mustache', 'r', encoding='utf-8') as f: parse_error += chevron.render(f, { 'format_error': True, 'format_string': info }).strip() html_params['parse_error'] = parse_error if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = pl.escape_unicode_string( raw_submitted_answer) partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) if display == 'inline': html_params['inline'] = True elif display == 'block': html_params['block'] = True else: raise ValueError( 'method of display "%s" is not valid (must be "inline" or "block")' % display) html_params['error'] = html_params['parse_error'] or html_params.get( 'missing_input', False) with open('pl-symbolic-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': a_tru = data['correct_answers'].get(name, None) if a_tru is not None: if isinstance(a_tru, str): # this is so instructors can specify the true answer simply as a string a_tru = phs.convert_string_to_sympy( a_tru, variables, allow_complex=allow_complex) else: a_tru = phs.json_to_sympy(a_tru, allow_complex=allow_complex) a_tru = a_tru.subs(sympy.I, sympy.Symbol(imaginary_unit)) html_params = { 'answer': True, 'label': label, 'a_tru': sympy.latex(a_tru) } with open('pl-symbolic-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) answer_name = pl.get_string_attrib(element, 'answers-name') grading_method = pl.get_string_attrib(element, 'grading-method', GRADING_METHOD_DEFAULT) if data['panel'] == 'question': mcq_options = [] student_previous_submission = [] submission_indent = [] student_submission_dict_list = [] answer_name = pl.get_string_attrib(element, 'answers-name') source_header = pl.get_string_attrib(element, 'source-header', SOURCE_HEADER_DEFAULT) solution_header = pl.get_string_attrib(element, 'solution-header', SOLUTION_HEADER_DEFAULT) mcq_options = data['params'][answer_name] mcq_options = filter_multiple_from_array(mcq_options, ['inner_html', 'uuid']) if answer_name in data['submitted_answers']: student_previous_submission = filter_multiple_from_array( data['submitted_answers'][answer_name], ['inner_html', 'uuid', 'indent']) mcq_options = [ opt for opt in mcq_options if opt not in filter_multiple_from_array( student_previous_submission, ['inner_html', 'uuid']) ] for index, option in enumerate(student_previous_submission): submission_indent = option.get('indent', None) if submission_indent is not None: submission_indent = (int(submission_indent) * TAB_SIZE_PX) + INDENT_OFFSET temp = { 'inner_html': option['inner_html'], 'indent': submission_indent, 'uuid': option['uuid'] } student_submission_dict_list.append(dict(temp)) dropzone_layout = pl.get_string_attrib(element, 'solution-placement', SOLUTION_PLACEMENT_DEFAULT) check_indentation = pl.get_boolean_attrib(element, 'indentation', INDENTION_DEFAULT) max_indent = pl.get_integer_attrib(element, 'max-indent', MAX_INDENTION_DEFAULT) inline_layout = pl.get_boolean_attrib(element, 'inline', INLINE_DEFAULT) help_text = 'Drag answer tiles into the answer area to the ' + dropzone_layout + '. ' if grading_method == 'unordered': help_text += '<br>Your answer ordering does not matter. ' elif grading_method != 'external': help_text += '<br>The ordering of your answer matters and is graded.' else: help_text += '<br>Your answer will be autograded; be sure to indent and order your answer properly.' if check_indentation: help_text += '<br><b>Your answer should be indented. </b> Indent your tiles by dragging them horizontally in the answer area.' uuid = pl.get_uuid() html_params = { 'question': True, 'answer_name': answer_name, 'options': mcq_options, 'source-header': source_header, 'solution-header': solution_header, 'submission_dict': student_submission_dict_list, 'dropzone_layout': 'pl-order-blocks-bottom' if dropzone_layout == 'bottom' else 'pl-order-blocks-right', 'check_indentation': 'true' if check_indentation else 'false', 'help_text': help_text, 'inline': 'inline' if inline_layout is True else None, 'max_indent': max_indent, 'uuid': uuid } with open('pl-order-blocks.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params) return html elif data['panel'] == 'submission': if grading_method == 'external': return '' # external grader is responsible for displaying results screen student_submission = '' score = None feedback = None if answer_name in data['submitted_answers']: student_submission = [{ 'inner_html': attempt['inner_html'], 'indent': ((attempt['indent'] or 0) * TAB_SIZE_PX) + INDENT_OFFSET } for attempt in data['submitted_answers'][answer_name]] if answer_name in data['partial_scores']: score = data['partial_scores'][answer_name]['score'] feedback = data['partial_scores'][answer_name]['feedback'] html_params = { 'submission': True, 'parse-error': data['format_errors'].get(answer_name, None), 'student_submission': student_submission, 'feedback': feedback } if score is not None: try: score = float(score * 100) if score >= 100: html_params['correct'] = True elif score > 0: html_params['partially_correct'] = math.floor(score) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score: ' + data['partial_scores'][answer_name]['score']) with open('pl-order-blocks.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params) return html elif data['panel'] == 'answer': if grading_method == 'external': try: base_path = data['options']['question_path'] file_lead_path = os.path.join(base_path, 'tests/ans.py') with open(file_lead_path, 'r') as file: solution_file = file.read() return f'<pl-code language="python">{solution_file}</pl-code>' except FileNotFoundError: return 'The reference solution is not provided for this question.' if grading_method == 'unordered': ordering_message = 'in any order' elif grading_method == 'dag' or grading_method == 'ranking': ordering_message = 'one possible correct order' else: ordering_message = 'in the specified order' check_indentation = pl.get_boolean_attrib(element, 'indentation', INDENTION_DEFAULT) indentation_message = ', with correct indentation' if check_indentation is True else None question_solution = [{ 'inner_html': solution['inner_html'], 'indent': ((solution['indent'] or 0) * TAB_SIZE_PX) + INDENT_OFFSET } for solution in data['correct_answers'][answer_name]] html_params = { 'true_answer': True, 'question_solution': question_solution, 'ordering_message': ordering_message, 'indentation_message': indentation_message } with open('pl-order-blocks.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params) return html else: raise Exception('Invalid panel type')
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) digits = pl.get_integer_attrib(element, 'digits', 2) show_matlab = pl.get_boolean_attrib(element, 'show-matlab', True) show_mathematica = pl.get_boolean_attrib(element, 'show-mathematica', True) show_python = pl.get_boolean_attrib(element, 'show-python', True) default_tab = pl.get_string_attrib(element, 'default-tab', 'matlab') tab_list = ['matlab', 'mathematica', 'python'] if default_tab not in tab_list: raise Exception(f'invalid default-tab: {default_tab}') # Setting the default tab displayed_tab = [show_matlab, show_mathematica, show_python] if not any(displayed_tab): raise Exception('All tabs have been hidden from display. At least one tab must be shown.') default_tab_index = tab_list.index(default_tab) # If not displayed, make first visible tab the default if not displayed_tab[default_tab_index]: first_display = displayed_tab.index(True) default_tab = tab_list[first_display] default_tab_index = tab_list.index(default_tab) # Active tab should be the default tab default_tab_list = [False, False, False] default_tab_list[default_tab_index] = True [active_tab_matlab, active_tab_mathematica, active_tab_python] = default_tab_list # Process parameter data matlab_data = '' mathematica_data = '' python_data = 'import numpy as np\n\n' for child in element: if child.tag == 'variable': # Raise exception if variable does not have a name pl.check_attribs(child, required_attribs=['params-name'], optional_attribs=['comment', 'digits']) # Get name of variable var_name = pl.get_string_attrib(child, 'params-name') # Get value of variable, raising exception if variable does not exist var_data = data['params'].get(var_name, None) if var_data is None: raise Exception('No value in data["params"] for variable %s in pl-variable-output element' % var_name) # If the variable is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) var_data = pl.from_json(var_data) # Get comment, if it exists var_matlab_comment = '' var_mathematica_comment = '' var_python_comment = '' if pl.has_attrib(child, 'comment'): var_comment = pl.get_string_attrib(child, 'comment') var_matlab_comment = f' % {var_comment}' var_mathematica_comment = f' (* {var_comment} *)' var_python_comment = f' # {var_comment}' # Get digit for child, if it exists if not pl.has_attrib(child, 'digits'): var_digits = digits else: var_digits = pl.get_string_attrib(child, 'digits') # Assembling Python array formatting if np.isscalar(var_data): prefix = '' suffix = '' else: # Wrap the variable in an ndarray (if it's already one, this does nothing) var_data = np.array(var_data) # Check shape of variable if var_data.ndim > 2: raise Exception('Value in data["params"] for variable %s in pl-variable-output element must be a scalar, a vector, or a 2D array' % var_name) # Create prefix/suffix so python string is np.array( ... ) prefix = 'np.array(' suffix = ')' # Mathematica reserved letters: C D E I K N O mathematica_reserved = ['C', 'D', 'E', 'I', 'K', 'N', 'O'] if pl.inner_html(child) in mathematica_reserved: mathematica_suffix = 'm' else: mathematica_suffix = '' # Create string for matlab and python format var_name_disp = pl.inner_html(child) var_matlab_data = pl.string_from_numpy(var_data, language='matlab', digits=var_digits) var_mathematica = pl.string_from_numpy(var_data, language='mathematica', digits=var_digits) var_python_data = pl.string_from_numpy(var_data, language='python', digits=var_digits) matlab_data += f'{var_name_disp} = {var_matlab_data};{var_matlab_comment}\n' mathematica_data += f'{var_name_disp}{mathematica_suffix} = {var_mathematica};{var_mathematica_comment}\n' python_data += f'{var_name_disp} = {prefix}{var_python_data}{suffix}{var_python_comment}\n' html_params = { 'active_tab_matlab': active_tab_matlab, 'active_tab_mathematica': active_tab_mathematica, 'active_tab_python': active_tab_python, 'show_matlab': show_matlab, 'show_mathematica': show_mathematica, 'show_python': show_python, 'matlab_data': matlab_data, 'mathematica_data': mathematica_data, 'python_data': python_data, 'uuid': pl.get_uuid() } with open('pl-variable-output.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') label = pl.get_string_attrib(element, 'label', LABEL_DEFAULT) suffix = pl.get_string_attrib(element, 'suffix', SUFFIX_DEFAULT) display = pl.get_string_attrib(element, 'display', DISPLAY_DEFAULT) remove_leading_trailing = pl.get_string_attrib( element, 'remove-leading-trailing', REMOVE_LEADING_TRAILING_DEFAULT) remove_spaces = pl.get_string_attrib(element, 'remove-spaces', REMOVE_SPACES_DEFAULT) placeholder = pl.get_string_attrib(element, 'placeholder', PLACEHOLDER_DEFAULT) if data['panel'] == 'question': editable = data['editable'] raw_submitted_answer = data['raw_submitted_answers'].get(name, None) if remove_leading_trailing: if remove_spaces: space_hint = 'All spaces will be removed from your answer.' else: space_hint = 'Leading and trailing spaces will be removed from your answer.' else: if remove_spaces: space_hint = 'All spaces between text will be removed but leading and trailing spaces will be left as part of your answer.' else: space_hint = 'Leading and trailing spaces will be left as part of your answer.' # Get info strings info_params = {'format': True, 'space_hint': space_hint} with open('pl-string-input.mustache', 'r', encoding='utf-8') as f: template = f.read() info = chevron.render(template, info_params).strip() info_params.pop('format', None) html_params = { 'question': True, 'name': name, 'label': label, 'suffix': suffix, 'remove-leading-trailing': remove_leading_trailing, 'remove-spaces': remove_spaces, 'editable': editable, 'info': info, 'placeholder': placeholder, 'size': pl.get_integer_attrib(element, 'size', SIZE_DEFAULT), 'show_info': pl.get_boolean_attrib(element, 'show-help-text', SHOW_HELP_TEXT_DEFAULT), 'uuid': pl.get_uuid() } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) html_params['display_append_span'] = html_params['show_info'] or suffix if display == 'inline': html_params['inline'] = True elif display == 'block': html_params['block'] = True else: raise ValueError( 'method of display "%s" is not valid (must be "inline" or "block")' % display) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) with open('pl-string-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'label': label, 'parse_error': parse_error, 'uuid': pl.get_uuid() } if parse_error is None and name in data['submitted_answers']: # Get submitted answer, raising an exception if it does not exist a_sub = data['submitted_answers'].get(name, None) if a_sub is None: raise Exception('submitted answer is None') # If answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_sub = pl.from_json(a_sub) a_sub = pl.escape_unicode_string(a_sub) html_params['suffix'] = suffix html_params['a_sub'] = a_sub elif name not in data['submitted_answers']: html_params['missing_input'] = True html_params['parse_error'] = None else: raw_submitted_answer = data['raw_submitted_answers'].get( name, None) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = pl.escape_unicode_string( raw_submitted_answer) partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) html_params['error'] = html_params['parse_error'] or html_params.get( 'missing_input', False) with open('pl-string-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is not None: html_params = { 'answer': True, 'label': label, 'a_tru': a_tru, 'suffix': suffix } with open('pl-string-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') label = pl.get_string_attrib(element, 'label', None) suffix = pl.get_string_attrib(element, 'suffix', None) display = pl.get_string_attrib(element, 'display', 'inline') if data['panel'] == 'question': editable = data['editable'] raw_submitted_answer = data['raw_submitted_answers'].get(name, None) # Get info strings info_params = {'format': True} with open('pl-integer-input.mustache', 'r', encoding='utf-8') as f: info = chevron.render(f, info_params).strip() with open('pl-integer-input.mustache', 'r', encoding='utf-8') as f: info_params.pop('format', None) info_params['shortformat'] = True shortinfo = chevron.render(f, info_params).strip() html_params = { 'question': True, 'name': name, 'label': label, 'suffix': suffix, 'editable': editable, 'info': info, 'shortinfo': shortinfo, 'uuid': pl.get_uuid() } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) if display == 'inline': html_params['inline'] = True elif display == 'block': html_params['block'] = True else: raise ValueError('method of display "%s" is not valid (must be "inline" or "block")' % display) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) with open('pl-integer-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'label': label, 'parse_error': parse_error, 'uuid': pl.get_uuid() } if parse_error is None: # Get submitted answer, raising an exception if it does not exist a_sub = data['submitted_answers'].get(name, None) if a_sub is None: raise Exception('submitted answer is None') # If answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_sub = pl.from_json(a_sub) html_params['suffix'] = suffix html_params['a_sub'] = '{:d}'.format(a_sub) else: raw_submitted_answer = data['raw_submitted_answers'].get(name, None) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl-integer-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is not None: html_params = {'answer': True, 'label': label, 'a_tru': '{:d}'.format(a_tru), 'suffix': suffix} with open('pl-integer-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) answer_name = pl.get_string_attrib(element, 'answer-name') uuid = pl.get_uuid() body_position = get_position(element, 'body-position', default=[0, 0, 0]) body_orientation = get_orientation(element, 'body-orientation', 'body-pose-format') camera_position = get_position(element, 'camera-position', default=[5, 2, 2], must_be_nonzero=True) body_cantranslate = pl.get_boolean_attrib(element, 'body-cantranslate', True) body_canrotate = pl.get_boolean_attrib(element, 'body-canrotate', True) camera_canmove = pl.get_boolean_attrib(element, 'camera-canmove', True) text_pose_format = pl.get_string_attrib(element, 'text-pose-format', 'matrix') if text_pose_format not in ['matrix', 'quaternion', 'homogeneous']: raise Exception('attribute "text-pose-format" must be either "matrix", "quaternion", or homogeneous') objects = get_objects(element, data) if data['panel'] == 'question': will_be_graded = pl.get_boolean_attrib(element, 'grade', True) show_pose = pl.get_boolean_attrib(element, 'show-pose-in-question', True) # Restore pose of body and camera, if available - otherwise use values # from attributes (note that restored pose will also have camera_orientation, # which we currently ignore because the camera is always z up and looking # at the origin of the space frame). # # Be careful. It's possible that data['submitted_answers'][answer_name] # exists but is None (due to some other error). So we need to use None # as the default and to check if the result - either from the existing # value or the default value - is None. pose_default = { 'body_quaternion': body_orientation, 'body_position': body_position, 'camera_position': camera_position } pose = data['submitted_answers'].get(answer_name, None) if pose is None: pose = pose_default # These are passed as arguments to PLThreeJS constructor in client code options = { 'uuid': uuid, 'pose': dict_to_b64(pose), 'pose_default': dict_to_b64(pose_default), 'body_cantranslate': body_cantranslate, 'body_canrotate': body_canrotate, 'camera_canmove': camera_canmove, 'text_pose_format': text_pose_format, 'objects': objects } # These are used for templating html_params = { 'question': True, 'uuid': uuid, 'answer_name': answer_name, 'show_bodybuttons': body_cantranslate or body_canrotate, 'show_toggle': body_cantranslate and body_canrotate, 'show_reset': body_cantranslate or body_canrotate or camera_canmove, 'show_pose': show_pose, 'show_instructions': will_be_graded, 'tol_translation': '{:.2f}'.format(pl.get_float_attrib(element, 'tol-translation', 0.5)), 'tol_rotation': '{:.1f}'.format(pl.get_float_attrib(element, 'tol-rotation', 5)), 'default_is_python': True, 'options': json.dumps(options, allow_nan=False) } with open('pl-threejs.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': will_be_graded = pl.get_boolean_attrib(element, 'grade', True) if not will_be_graded: return '' show_pose = pl.get_boolean_attrib(element, 'show-pose-in-submitted-answer', True) # Get submitted answer pose = data['submitted_answers'].get(answer_name) # These are passed as arguments to PLThreeJS constructor in client code options = { 'uuid': uuid, 'pose': dict_to_b64(pose), 'body_cantranslate': False, 'body_canrotate': False, 'camera_canmove': False, 'text_pose_format': text_pose_format, 'objects': objects } # These are used for templating html_params = { 'submission': True, 'uuid': uuid, 'answer_name': answer_name, 'show_bodybuttons': False, 'show_toggle': False, 'show_pose': show_pose, 'default_is_python': True, 'options': json.dumps(options, allow_nan=False) } partial_score = data['partial_scores'].get(answer_name, None) if partial_score is not None: html_params['error_in_translation'] = str(np.abs(np.round(partial_score['feedback']['error_in_translation'], 2))) html_params['error_in_rotation'] = str(np.abs(np.round(partial_score['feedback']['error_in_rotation'], 1))) html_params['show_feedback'] = True score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl-threejs.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': will_be_graded = pl.get_boolean_attrib(element, 'grade', True) if not will_be_graded: return '' show_pose = pl.get_boolean_attrib(element, 'show-pose-in-correct-answer', True) # Get submitted answer pose = data['submitted_answers'].get(answer_name, None) if pose is None: # If we are here, an error has occurred. Replace pose with its default. # (Only pose['camera_position'] is actually used.) pose = { 'body_quaternion': body_orientation, 'body_position': body_position, 'camera_position': camera_position } # Get correct answer a = data['correct_answers'].get(answer_name, None) if a is None: return '' # Convert correct answer to Quaternion, then to [x, y, z, w] f = pl.get_string_attrib(element, 'answer-pose-format', 'rpy') p, q = parse_correct_answer(f, a) p = p.tolist() q = np.roll(q.elements, -1).tolist() # Replace body pose with correct answer pose['body_position'] = p pose['body_quaternion'] = q # These are passed as arguments to PLThreeJS constructor in client code options = { 'uuid': uuid, 'pose': dict_to_b64(pose), 'body_cantranslate': False, 'body_canrotate': False, 'camera_canmove': False, 'text_pose_format': text_pose_format, 'objects': objects } # These are used for templating html_params = { 'answer': True, 'uuid': uuid, 'answer_name': answer_name, 'show_bodybuttons': False, 'show_toggle': False, 'show_pose': show_pose, 'default_is_python': True, 'options': json.dumps(options, allow_nan=False) } with open('pl-threejs.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def render(element_html, data): if data['panel'] == 'submission': html_params = {'submission': True, 'graded': True, 'uuid': pl.get_uuid()} feedback = data['feedback'] html_params['graded'] = bool(feedback) grading_succeeded = bool(feedback.get('succeeded', None)) html_params['grading_succeeded'] = grading_succeeded if not grading_succeeded: html_params['message'] = feedback.get('message', None) else: results = feedback.get('results', None) if grading_succeeded and results: html_params['succeeded'] = bool(results.get('succeeded', None)) html_params['score'] = format(results.get('score', 0) * 100, '.2f').rstrip('0').rstrip('.') html_params['achieved_max_points'] = (results.get('score', 0) >= 1.0) html_params['results_color'] = '#4CAF50' if (results.get('score', 0) >= 1.0) else '#F44336' html_params['has_message'] = bool(results.get('message', False)) html_params['message'] = results.get('message', None) html_params['has_output'] = bool(results.get('output', False)) html_params['output'] = results.get('output', None) html_params['has_message_or_output'] = bool(html_params['has_message'] or html_params['has_output']) results_tests = results.get('tests', None) html_params['has_tests'] = bool(results.get('tests', None)) if results_tests: # Let's not assume that people give us a valid array of tests # If any test is missing either points or max_points, we'll # disable detailed scores for all questions tests_missing_points = False for test in results_tests: if test.get('points', None) is None: tests_missing_points = True if test.get('max_points', None) is None: tests_missing_points = True html_params['tests_missing_points'] = tests_missing_points if not tests_missing_points: html_params['points'] = sum(test['points'] for test in results_tests) html_params['max_points'] = sum(test['max_points'] for test in results_tests) # We need to build a new tests array to massage data a bit tests = [] for index, results_test in enumerate(results_tests): test = {} test['index'] = index test['name'] = results_test.get('name', '') test['has_message'] = bool(results_test.get('message', None)) test['message'] = results_test.get('message', None) test['has_output'] = bool(results_test.get('output', None)) test['output'] = results_test.get('output', None) test['has_description'] = bool(results_test.get('description', None)) test['description'] = results_test.get('description', None) if not tests_missing_points: test['max_points'] = results_test.get('max_points') test['points'] = results_test.get('points') correct = test['max_points'] == test['points'] test['results_color'] = '#4CAF50' if correct else '#F44336' test['results_icon'] = 'fa-check' if correct else 'fa-times' tests.append(test) html_params['tests'] = tests with open('pl-external-grader-results.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' return html
def render(element_html, data): if data['panel'] != 'question': return '' element = lxml.html.fragment_fromstring(element_html) file_name = pl.get_string_attrib(element, 'file-name', '') answer_name = get_answer_name(file_name) editor_config_function = pl.get_string_attrib(element, 'editor-config-function', None) ace_mode = pl.get_string_attrib(element, 'ace-mode', None) ace_theme = pl.get_string_attrib(element, 'ace-theme', None) uuid = pl.get_uuid() source_file_name = pl.get_string_attrib(element, 'source-file-name', None) min_lines = pl.get_integer_attrib(element, 'min-lines', None) max_lines = pl.get_integer_attrib(element, 'max-lines', None) auto_resize = pl.get_string_attrib(element, 'auto-resize', 'false') # If auto_resize is set but min_lines isn't, the height of the # file editor area will be set to 1 line. Thus, we need to set # a default of about 18 lines to match an editor window without # the auto resizing enabled. if min_lines is None and auto_resize == 'true': min_lines = 18 html_params = { 'name': answer_name, 'file_name': file_name, 'ace_mode': ace_mode, 'ace_theme': ace_theme, 'editor_config_function': editor_config_function, 'min_lines': min_lines, 'max_lines': max_lines, 'auto_resize': auto_resize, 'uuid': uuid } if source_file_name is not None: file_path = os.path.join(data['options']['question_path'], source_file_name) text_display = open(file_path).read() else: if element.text is not None: text_display = str(element.text) else: text_display = '' html_params['original_file_contents'] = base64.b64encode( text_display.encode('UTF-8').strip()).decode() submitted_file_contents = data['submitted_answers'].get(answer_name, None) if submitted_file_contents: html_params['current_file_contents'] = submitted_file_contents else: html_params['current_file_contents'] = html_params[ 'original_file_contents'] if data['panel'] == 'question': html_params['question'] = True with open('pl-file-editor.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) answer_name = pl.get_string_attrib(element, 'answers-name') if data['panel'] == 'question': mcq_options = [] # stores MCQ options student_previous_submission = [] submission_indent = [] for html_tags in element: if html_tags.tag == 'pl-answer': mcq_options.append( html_tags.text ) # store the original specified ordering of all the MCQ options answer_name = pl.get_string_attrib(element, 'answers-name') source_header = pl.get_string_attrib(element, 'source-header', SOURCE_HEADER_DEFAULT) solution_header = pl.get_string_attrib(element, 'solution-header', SOLUTION_HEADER_DEFAULT) student_submission_dict_list = [] mcq_options = data['params'][answer_name] if answer_name in data['submitted_answers']: student_previous_submission = data['submitted_answers'][ answer_name]['student_raw_submission'] mcq_options = list( set(mcq_options) - set(student_previous_submission)) for index, mcq_options_text in enumerate(student_previous_submission): # render the answers column (restore the student submission) submission_indent = data['submitted_answers'][answer_name][ 'student_answer_indent'][index] submission_indent = (int(submission_indent) * 50) + 10 temp = {'text': mcq_options_text, 'indent': submission_indent} student_submission_dict_list.append(dict(temp)) dropzone_layout = pl.get_string_attrib(element, 'solution-placement', SOLUTION_PLACEMENT_DEFAULT) check_indentation = pl.get_boolean_attrib(element, 'indentation', INDENTION_DEFAULT) html_params = { 'question': True, 'answer_name': answer_name, 'options': mcq_options, 'source-header': source_header, 'solution-header': solution_header, 'submission_dict': student_submission_dict_list, 'dropzone_layout': 'pl-order-blocks-bottom' if dropzone_layout == 'bottom' else 'pl-order-blocks-right', 'check_indentation': 'enableIndentation' if check_indentation is True else None } with open('pl-order-blocks.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params) return html elif data['panel'] == 'submission': if pl.get_string_attrib(element, 'grading-method', 'ordered') == 'external': return '' # render the submission panel uuid = pl.get_uuid() student_submission = '' color = 'badge-danger' score = 0 feedback = None if answer_name in data['submitted_answers']: student_submission = data['submitted_answers'][answer_name][ 'student_raw_submission'] if answer_name in data['partial_scores']: color = render_html_color( data['partial_scores'][answer_name]['score']) score = data['partial_scores'][answer_name]['score'] * 100 feedback = data['partial_scores'][answer_name]['feedback'] html_params = { 'submission': True, 'uuid': uuid, 'parse-error': data['format_errors'].get(answer_name, None), 'student_submission': pretty_print(student_submission), 'color': color, 'score': score, 'perfect_score': True if score == 100 else None, 'feedback': feedback } # Finally, render the HTML with open('pl-order-blocks.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params) return html elif data['panel'] == 'answer': if pl.get_string_attrib(element, 'grading-method', 'ordered') == 'external': try: base_path = data['options']['question_path'] file_lead_path = os.path.join(base_path, 'tests/ans.py') with open(file_lead_path, 'r') as file: solution_file = file.read() return f'<pl-code language="python">{solution_file}</pl-code>' except FileNotFoundError: return 'The reference solution is not provided for this question.' grading_mode = pl.get_string_attrib(element, 'grading-method', 'ordered') grading_mode = 'in any order' if grading_mode == 'unordered' else 'in the specified order' check_indentation = pl.get_boolean_attrib(element, 'indentation', INDENTION_DEFAULT) check_indentation = ', with correct indentation' if check_indentation is True else None if answer_name in data['correct_answers']: html_params = { 'true_answer': True, 'question_solution': pretty_print( data['correct_answers'][answer_name]['correct_answers']), 'grading_mode': grading_mode, 'check_indentation': check_indentation } with open('pl-order-blocks.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params) return html else: return ''
def render(element_html, data): if data['panel'] != 'question': return '' element = lxml.html.fragment_fromstring(element_html) file_name = pl.get_string_attrib(element, 'file-name', '') answer_name = get_answer_name(file_name) editor_config_function = pl.get_string_attrib( element, 'editor-config-function', EDITOR_CONFIG_FUNCTION_DEFAULT) ace_mode = pl.get_string_attrib(element, 'ace-mode', ACE_MODE_DEFAULT) ace_theme = pl.get_string_attrib(element, 'ace-theme', ACE_THEME_DEFAULT) uuid = pl.get_uuid() source_file_name = pl.get_string_attrib(element, 'source-file-name', SOURCE_FILE_NAME_DEFAULT) min_lines = pl.get_integer_attrib(element, 'min-lines', MIN_LINES_DEFAULT) max_lines = pl.get_integer_attrib(element, 'max-lines', MAX_LINES_DEFAULT) auto_resize = pl.get_boolean_attrib(element, 'auto-resize', AUTO_RESIZE_DEFAULT) preview = pl.get_string_attrib(element, 'preview', PREVIEW_DEFAULT) focus = pl.get_boolean_attrib(element, 'focus', FOCUS_DEFAULT) # stringify boolean attributes (needed when written to html_params) auto_resize = 'true' if auto_resize else 'false' focus = 'true' if focus else 'false' # If auto_resize is set but min_lines isn't, the height of the # file editor area will be set to 1 line. Thus, we need to set # a default of about 18 lines to match an editor window without # the auto resizing enabled. if min_lines is None and auto_resize == 'true': min_lines = 18 html_params = { 'name': answer_name, 'file_name': file_name, 'ace_mode': ace_mode, 'ace_theme': ace_theme, 'editor_config_function': editor_config_function, 'min_lines': min_lines, 'max_lines': max_lines, 'auto_resize': auto_resize, 'preview': preview, 'uuid': uuid, 'focus': focus } if source_file_name is not None: file_path = os.path.join(data['options']['question_path'], source_file_name) text_display = open(file_path).read() else: if element.text is not None: text_display = str(element.text) else: text_display = '' html_params['original_file_contents'] = base64.b64encode( text_display.encode('UTF-8').strip()).decode() submitted_file_contents = data['submitted_answers'].get(answer_name, None) if submitted_file_contents: html_params['current_file_contents'] = submitted_file_contents else: html_params['current_file_contents'] = html_params[ 'original_file_contents'] if data['panel'] == 'question': html_params['question'] = True with open('pl-file-editor.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' return html
def get_graph_info(html_tags): tag = pl.get_string_attrib(html_tags, 'tag', pl.get_uuid()).strip() depends = pl.get_string_attrib(html_tags, 'depends', '') depends = [tag.strip() for tag in depends.split(',')] if depends else [] return tag, depends
def prepare(element_html, data): element = lxml.html.fragment_fromstring(element_html) answer_name = pl.get_string_attrib(element, 'answers-name') required_attribs = ['answers-name'] optional_attribs = [ 'source-blocks-order', 'grading-method', 'indentation', 'source-header', 'solution-header', 'file-name', 'solution-placement', 'max-incorrect', 'min-incorrect', 'weight', 'inline', 'max-indent', 'feedback', 'partial-credit' ] pl.check_attribs(element, required_attribs=required_attribs, optional_attribs=optional_attribs) check_indentation = pl.get_boolean_attrib(element, 'indentation', INDENTION_DEFAULT) grading_method = pl.get_string_attrib(element, 'grading-method', GRADING_METHOD_DEFAULT) feedback_type = pl.get_string_attrib(element, 'feedback', FEEDBACK_DEFAULT) if grading_method in ['dag', 'ranking']: partial_credit_type = pl.get_string_attrib(element, 'partial-credit', 'lcs') if partial_credit_type not in ['none', 'lcs']: raise Exception('partial credit type "' + partial_credit_type + '" is not available with the "' + grading_method + '" grading-method.') elif pl.get_string_attrib(element, 'partial-credit', None) is not None: raise Exception( 'You may only specify partial credit options in the DAG and ranking grading modes.' ) accepted_grading_method = [ 'ordered', 'unordered', 'ranking', 'dag', 'external' ] if grading_method not in accepted_grading_method: raise Exception( 'The grading-method attribute must be one of the following: ' + ', '.join(accepted_grading_method)) if (grading_method not in ['dag', 'ranking'] and feedback_type != 'none') or \ (grading_method in ['dag', 'ranking'] and feedback_type not in ['none', 'first-wrong']): raise Exception('feedback type "' + feedback_type + '" is not available with the "' + grading_method + '" grading-method.') correct_answers = [] incorrect_answers = [] def prepare_tag(html_tags, index, group_info={ 'tag': None, 'depends': None }): if html_tags.tag != 'pl-answer': raise Exception( 'Any html tags nested inside <pl-order-blocks> must be <pl-answer> or <pl-block-group>. \ Any html tags nested inside <pl-block-group> must be <pl-answer>' ) if grading_method == 'external': pl.check_attribs(html_tags, required_attribs=[], optional_attribs=['correct']) elif grading_method in ['unordered', 'ordered']: pl.check_attribs(html_tags, required_attribs=[], optional_attribs=['correct', 'indent']) elif grading_method == 'ranking': pl.check_attribs(html_tags, required_attribs=[], optional_attribs=['correct', 'ranking', 'indent']) elif grading_method == 'dag': pl.check_attribs(html_tags, required_attribs=[], optional_attribs=[ 'correct', 'tag', 'depends', 'comment', 'indent' ]) is_correct = pl.get_boolean_attrib(html_tags, 'correct', PL_ANSWER_CORRECT_DEFAULT) answer_indent = pl.get_integer_attrib(html_tags, 'indent', None) inner_html = pl.inner_html(html_tags) ranking = pl.get_integer_attrib(html_tags, 'ranking', -1) tag, depends = get_graph_info(html_tags) if grading_method == 'ranking': tag = str(index) if check_indentation is False and answer_indent is not None: raise Exception( '<pl-answer> should not specify indentation if indentation is disabled.' ) answer_data_dict = { 'inner_html': inner_html, 'indent': answer_indent, 'ranking': ranking, 'index': index, 'tag': tag, # set by HTML with DAG grader, set internally for ranking grader 'depends': depends, # only used with DAG grader 'group_info': group_info # only used with DAG grader } if is_correct: correct_answers.append(answer_data_dict) else: incorrect_answers.append(answer_data_dict) index = 0 for html_tags in element: # iterate through the html tags inside pl-order-blocks if html_tags.tag is etree.Comment: continue elif html_tags.tag == 'pl-block-group': if grading_method != 'dag': raise Exception( 'Block groups only supported in the "dag" grading mode.') group_tag, group_depends = get_graph_info(html_tags) for grouped_tag in html_tags: if html_tags.tag is etree.Comment: continue else: prepare_tag(grouped_tag, index, { 'tag': group_tag, 'depends': group_depends }) index += 1 else: prepare_tag(html_tags, index) index += 1 if grading_method != 'external' and len(correct_answers) == 0: raise Exception( 'There are no correct answers specified for this question.') all_incorrect_answers = len(incorrect_answers) max_incorrect = pl.get_integer_attrib(element, 'max-incorrect', all_incorrect_answers) min_incorrect = pl.get_integer_attrib(element, 'min-incorrect', all_incorrect_answers) if min_incorrect > len(incorrect_answers) or max_incorrect > len( incorrect_answers): raise Exception( 'The min-incorrect or max-incorrect attribute may not exceed the number of incorrect <pl-answers>.' ) if min_incorrect > max_incorrect: raise Exception( 'The attribute min-incorrect must be smaller than max-incorrect.') incorrect_answers_count = random.randint(min_incorrect, max_incorrect) sampled_correct_answers = correct_answers sampled_incorrect_answers = random.sample(incorrect_answers, incorrect_answers_count) mcq_options = sampled_correct_answers + sampled_incorrect_answers source_blocks_order = pl.get_string_attrib(element, 'source-blocks-order', SOURCE_BLOCKS_ORDER_DEFAULT) if source_blocks_order == 'random': random.shuffle(mcq_options) elif source_blocks_order == 'ordered': mcq_options.sort(key=lambda a: a['index']) else: raise Exception( 'The specified option for the "source-blocks-order" attribute is invalid.' ) for option in mcq_options: option['uuid'] = pl.get_uuid() data['params'][answer_name] = mcq_options data['correct_answers'][answer_name] = correct_answers # if the order of the blocks in the HTML is a correct solution, leave it unchanged, but if it # isn't we need to change it into a solution before displaying it as such data_copy = deepcopy(data) data_copy['submitted_answers'] = {answer_name: correct_answers} data_copy['partial_scores'] = {} grade(element_html, data_copy) if data_copy['partial_scores'][answer_name]['score'] != 1: data['correct_answers'][answer_name] = solve_problem( correct_answers, grading_method)
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) answer_name = pl.get_string_attrib(element, 'answer-name') uuid = pl.get_uuid() body_position = get_position(element, 'body-position', default=BODY_POSITION_DEFAULT) body_orientation = get_orientation(element, 'body-orientation', BODY_ORIENTATION_DEFAULT) camera_position = get_position(element, 'camera-position', default=CAMERA_POSITION_DEFAULT, must_be_nonzero=True) body_cantranslate = pl.get_boolean_attrib(element, 'body-cantranslate', BODY_CANTRANSLATE_DEFAULT) body_canrotate = pl.get_boolean_attrib(element, 'body-canrotate', BODY_CANROTATE_DEFAULT) camera_canmove = pl.get_boolean_attrib(element, 'camera-canmove', CAMERA_CANMOVE_DEFAULT) text_pose_format = pl.get_string_attrib(element, 'text-pose-format', TEXT_POSE_FORMAT_DEFAULT) if text_pose_format not in ['matrix', 'quaternion', 'homogeneous']: raise Exception( 'attribute "text-pose-format" must be either "matrix", "quaternion", or homogeneous' ) objects = get_objects(element, data) if data['panel'] == 'question': will_be_graded = pl.get_boolean_attrib(element, 'grade', GRADE_DEFAULT) show_pose = pl.get_boolean_attrib(element, 'show-pose-in-question', SHOW_POSE_IN_QUESTION_DEFAULT) # Restore pose of body and camera, if available - otherwise use values # from attributes (note that restored pose will also have camera_orientation, # which we currently ignore because the camera is always z up and looking # at the origin of the space frame). # # Be careful. It's possible that data['submitted_answers'][answer_name] # exists but is None (due to some other error). So we need to use None # as the default and to check if the result - either from the existing # value or the default value - is None. pose_default = { 'body_quaternion': body_orientation, 'body_position': body_position, 'camera_position': camera_position } pose = data['submitted_answers'].get(answer_name, None) if pose is None: pose = pose_default # These are passed as arguments to PLThreeJS constructor in client code options = { 'uuid': uuid, 'pose': dict_to_b64(pose), 'pose_default': dict_to_b64(pose_default), 'body_cantranslate': body_cantranslate, 'body_canrotate': body_canrotate, 'camera_canmove': camera_canmove, 'text_pose_format': text_pose_format, 'objects': objects } # These are used for templating html_params = { 'question': True, 'uuid': uuid, 'answer_name': answer_name, 'show_bodybuttons': body_cantranslate or body_canrotate, 'show_toggle': body_cantranslate and body_canrotate, 'show_reset': body_cantranslate or body_canrotate or camera_canmove, 'show_pose': show_pose, 'show_instructions': will_be_graded, 'tol_translation': '{:.2f}'.format( pl.get_float_attrib(element, 'tol-translation', 0.5)), 'tol_rotation': '{:.1f}'.format(pl.get_float_attrib(element, 'tol-rotation', 5)), 'default_is_python': True, 'options': json.dumps(options, allow_nan=False) } with open('pl-threejs.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': will_be_graded = pl.get_boolean_attrib(element, 'grade', GRADE_DEFAULT) if not will_be_graded: return '' show_pose = pl.get_boolean_attrib( element, 'show-pose-in-submitted-answer', SHOW_POSE_IN_SUBMITTED_ANSWER_DEFAULT) # Get submitted answer pose = data['submitted_answers'].get(answer_name) # These are passed as arguments to PLThreeJS constructor in client code options = { 'uuid': uuid, 'pose': dict_to_b64(pose), 'body_cantranslate': False, 'body_canrotate': False, 'camera_canmove': False, 'text_pose_format': text_pose_format, 'objects': objects } # These are used for templating html_params = { 'submission': True, 'uuid': uuid, 'answer_name': answer_name, 'show_bodybuttons': False, 'show_toggle': False, 'show_pose': show_pose, 'default_is_python': True, 'options': json.dumps(options, allow_nan=False) } partial_score = data['partial_scores'].get(answer_name, None) if partial_score is not None: html_params['error_in_translation'] = str( np.abs( np.round(partial_score['feedback']['error_in_translation'], 2))) html_params['error_in_rotation'] = str( np.abs( np.round(partial_score['feedback']['error_in_rotation'], 1))) html_params['show_feedback'] = True score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl-threejs.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': will_be_graded = pl.get_boolean_attrib(element, 'grade', GRADE_DEFAULT) if not will_be_graded: return '' show_pose = pl.get_boolean_attrib(element, 'show-pose-in-correct-answer', SHOW_POSE_IN_CORRECT_ANSWER_DEFAULT) # Get submitted answer pose = data['submitted_answers'].get(answer_name, None) if pose is None: # If we are here, an error has occurred. Replace pose with its default. # (Only pose['camera_position'] is actually used.) pose = { 'body_quaternion': body_orientation, 'body_position': body_position, 'camera_position': camera_position } # Get correct answer a = data['correct_answers'].get(answer_name, None) if a is None: return '' # Convert correct answer to Quaternion, then to [x, y, z, w] f = pl.get_string_attrib(element, 'answer-pose-format', ANSWER_POSE_FORMAT_DEFAULT) p, q = parse_correct_answer(f, a) p = p.tolist() q = np.roll(q.elements, -1).tolist() # Replace body pose with correct answer pose['body_position'] = p pose['body_quaternion'] = q # These are passed as arguments to PLThreeJS constructor in client code options = { 'uuid': uuid, 'pose': dict_to_b64(pose), 'body_cantranslate': False, 'body_canrotate': False, 'camera_canmove': False, 'text_pose_format': text_pose_format, 'objects': objects } # These are used for templating html_params = { 'answer': True, 'uuid': uuid, 'answer_name': answer_name, 'show_bodybuttons': False, 'show_toggle': False, 'show_pose': show_pose, 'default_is_python': True, 'options': json.dumps(options, allow_nan=False) } with open('pl-threejs.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) # get the name of the element, in this case, the name of the array name = pl.get_string_attrib(element, 'answers-name') label = pl.get_string_attrib(element, 'label', None) allow_partial_credit = pl.get_boolean_attrib(element, 'allow-partial-credit', False) allow_feedback = pl.get_boolean_attrib(element, 'allow-feedback', allow_partial_credit) if data['panel'] == 'question': editable = data['editable'] # Get true answer a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is None: raise Exception( 'No value in data["correct_answers"] for variable %s in pl-matrix-component-input element' % name) else: if np.isscalar(a_tru): raise Exception( 'Value in data["correct_answers"] for variable %s in pl-matrix-component-input element cannot be a scalar.' % name) else: a_tru = np.array(a_tru) if a_tru.ndim != 2: raise Exception( 'Value in data["correct_answers"] for variable %s in pl-matrix-component-input element must be a 2D array.' % name) else: m, n = np.shape(a_tru) input_array = createTableForHTMLDisplay(m, n, name, label, data, 'input') # Get comparison parameters and info strings comparison = pl.get_string_attrib(element, 'comparison', 'relabs') if comparison == 'relabs': rtol = pl.get_float_attrib(element, 'rtol', 1e-2) atol = pl.get_float_attrib(element, 'atol', 1e-8) if (rtol < 0): raise ValueError( 'Attribute rtol = {:g} must be non-negative'.format(rtol)) if (atol < 0): raise ValueError( 'Attribute atol = {:g} must be non-negative'.format(atol)) info_params = { 'format': True, 'relabs': True, 'rtol': '{:g}'.format(rtol), 'atol': '{:g}'.format(atol) } elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) if (digits < 0): raise ValueError( 'Attribute digits = {:d} must be non-negative'.format( digits)) info_params = { 'format': True, 'sigfig': True, 'digits': '{:d}'.format(digits), 'comparison_eps': 0.51 * (10**-(digits - 1)) } elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) if (digits < 0): raise ValueError( 'Attribute digits = {:d} must be non-negative'.format( digits)) info_params = { 'format': True, 'decdig': True, 'digits': '{:d}'.format(digits), 'comparison_eps': 0.51 * (10**-(digits - 0)) } else: raise ValueError( 'method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: info = chevron.render(f, info_params).strip() with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: info_params.pop('format', None) info_params['shortformat'] = True shortinfo = chevron.render(f, info_params).strip() html_params = { 'question': True, 'name': name, 'label': label, 'editable': editable, 'info': info, 'shortinfo': shortinfo, 'input_array': input_array, 'inline': True, 'uuid': pl.get_uuid() } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'label': label, 'parse_error': parse_error, 'uuid': pl.get_uuid() } a_tru = pl.from_json(data['correct_answers'].get(name, None)) m, n = np.shape(a_tru) partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) if parse_error is None: # Get submitted answer, raising an exception if it does not exist a_sub = data['submitted_answers'].get(name, None) if a_sub is None: raise Exception('submitted answer is None') # If answer is in a format generated by pl.to_json, convert it back to a standard type (otherwise, do nothing) a_sub = pl.from_json(a_sub) # Wrap answer in an ndarray (if it's already one, this does nothing) a_sub = np.array(a_sub) # Format submitted answer as a latex string sub_latex = '$' + pl.latex_from_2darray( a_sub, presentation_type='g', digits=12) + '$' # When allowing feedback, display submitted answers using html table sub_html_table = createTableForHTMLDisplay(m, n, name, label, data, 'output-feedback') if allow_feedback and score is not None: if score < 1: html_params['a_sub_feedback'] = sub_html_table else: html_params['a_sub'] = sub_latex else: html_params['a_sub'] = sub_latex else: # create html table to show submitted answer when there is an invalid format html_params['raw_submitted_answer'] = createTableForHTMLDisplay( m, n, name, label, data, 'output-invalid') with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': # Get true answer - do nothing if it does not exist a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is not None: a_tru = np.array(a_tru) # Get comparison parameters and create the display data comparison = pl.get_string_attrib(element, 'comparison', 'relabs') if comparison == 'relabs': rtol = pl.get_float_attrib(element, 'rtol', 1e-2) atol = pl.get_float_attrib(element, 'atol', 1e-8) # FIXME: render correctly with respect to rtol and atol latex_data = '$' + pl.latex_from_2darray( a_tru, presentation_type='g', digits=12) + '$' elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) latex_data = '$' + pl.latex_from_2darray( a_tru, presentation_type='sigfig', digits=digits) + '$' elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) latex_data = '$' + pl.latex_from_2darray( a_tru, presentation_type='f', digits=digits) + '$' else: raise ValueError( 'method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) html_params = { 'answer': True, 'label': label, 'latex_data': latex_data, 'uuid': pl.get_uuid() } with open('pl-matrix-component-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') label = pl.get_string_attrib(element, 'label', None) suffix = pl.get_string_attrib(element, 'suffix', None) display = pl.get_string_attrib(element, 'display', 'inline') remove_leading_trailing = pl.get_string_attrib(element, 'remove-leading-trailing', False) remove_spaces = pl.get_string_attrib(element, 'remove-spaces', False) placeholder = pl.get_string_attrib(element, 'placeholder', None) if data['panel'] == 'question': editable = data['editable'] raw_submitted_answer = data['raw_submitted_answers'].get(name, None) # Get info strings info_params = {'format': True} with open('pl-string-input.mustache', 'r', encoding='utf-8') as f: template = f.read() info = chevron.render(template, info_params).strip() info_params.pop('format', None) html_params = { 'question': True, 'name': name, 'label': label, 'suffix': suffix, 'remove-leading-trailing': remove_leading_trailing, 'remove-spaces': remove_spaces, 'editable': editable, 'info': info, 'placeholder': placeholder, 'uuid': pl.get_uuid() } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) if display == 'inline': html_params['inline'] = True elif display == 'block': html_params['block'] = True else: raise ValueError( 'method of display "%s" is not valid (must be "inline" or "block")' % display) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) with open('pl-string-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'label': label, 'parse_error': parse_error, 'uuid': pl.get_uuid() } if parse_error is None: # Get submitted answer, raising an exception if it does not exist a_sub = data['submitted_answers'].get(name, None) if a_sub is None: raise Exception('submitted answer is None') # If answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_sub = pl.from_json(a_sub) html_params['suffix'] = suffix html_params['a_sub'] = a_sub else: raw_submitted_answer = data['raw_submitted_answers'].get( name, None) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape( raw_submitted_answer) partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl-string-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is not None: html_params = { 'answer': True, 'label': label, 'a_tru': a_tru, 'suffix': suffix } with open('pl-string-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') label = pl.get_string_attrib(element, 'label', None) suffix = pl.get_string_attrib(element, 'suffix', None) display = pl.get_string_attrib(element, 'display', DISPLAY_DEFAULT) if data['panel'] == 'question': editable = data['editable'] raw_submitted_answer = data['raw_submitted_answers'].get(name, None) html_params = { 'question': True, 'name': name, 'label': label, 'suffix': suffix, 'editable': editable, 'size': pl.get_integer_attrib(element, 'size', SIZE_DEFAULT), 'uuid': pl.get_uuid() } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) # Get comparison parameters and info strings comparison = pl.get_string_attrib(element, 'comparison', COMPARISON_DEFAULT) if comparison == 'relabs': rtol = pl.get_float_attrib(element, 'rtol', RTOL_DEFAULT) atol = pl.get_float_attrib(element, 'atol', ATOL_DEFAULT) if (rtol < 0): raise ValueError( 'Attribute rtol = {:g} must be non-negative'.format(rtol)) if (atol < 0): raise ValueError( 'Attribute atol = {:g} must be non-negative'.format(atol)) info_params = { 'format': True, 'relabs': True, 'rtol': '{:g}'.format(rtol), 'atol': '{:g}'.format(atol) } elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', DIGITS_DEFAULT) if (digits < 0): raise ValueError( 'Attribute digits = {:d} must be non-negative'.format( digits)) info_params = { 'format': True, 'sigfig': True, 'digits': '{:d}'.format(digits), 'comparison_eps': 0.51 * (10**-(digits - 1)) } elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', DIGITS_DEFAULT) if (digits < 0): raise ValueError( 'Attribute digits = {:d} must be non-negative'.format( digits)) info_params = { 'format': True, 'decdig': True, 'digits': '{:d}'.format(digits), 'comparison_eps': 0.51 * (10**-(digits - 0)) } else: raise ValueError( 'method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) # Update parameters for the info popup show_correct = 'correct' in html_params and pl.get_boolean_attrib( element, 'show-correct-answer', SHOW_CORRECT_ANSWER_DEFAULT) info_params['allow_complex'] = pl.get_boolean_attrib( element, 'allow-complex', ALLOW_COMPLEX_DEFAULT) info_params['show_info'] = pl.get_boolean_attrib( element, 'show-help-text', SHOW_HELP_TEXT_DEFAULT) info_params['show_correct'] = show_correct # Find the true answer to be able to display it in the info popup ans_true = None if pl.get_boolean_attrib(element, 'show-correct-answer', SHOW_CORRECT_ANSWER_DEFAULT): ans_true = format_true_ans(element, data, name) if ans_true is not None: info_params['a_tru'] = ans_true with open('pl-number-input.mustache', 'r', encoding='utf-8') as f: info = chevron.render(f, info_params).strip() with open('pl-number-input.mustache', 'r', encoding='utf-8') as f: info_params.pop('format', None) # Within mustache, the shortformat generates the shortinfo that is used as a placeholder inside of the numeric entry. # Here we opt to not generate the value, hence the placeholder is empty. info_params['shortformat'] = pl.get_boolean_attrib( element, 'show-placeholder', SHOW_PLACEHOLDER_DEFAULT) shortinfo = chevron.render(f, info_params).strip() html_params['info'] = info html_params['shortinfo'] = shortinfo # Determine the title of the popup based on what information is being shown if pl.get_boolean_attrib(element, 'show-help-text', SHOW_HELP_TEXT_DEFAULT): html_params['popup_title'] = 'Number' else: html_params['popup_title'] = 'Correct Answer' # Enable or disable the popup if pl.get_boolean_attrib(element, 'show-help-text', SHOW_HELP_TEXT_DEFAULT) or show_correct: html_params['show_info'] = True html_params[ 'display_append_span'] = 'questionmark' in html_params or suffix if display == 'inline': html_params['inline'] = True elif display == 'block': html_params['block'] = True else: raise ValueError( 'method of display "%s" is not valid (must be "inline" or "block")' % display) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) with open('pl-number-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'label': label, 'parse_error': parse_error, 'uuid': pl.get_uuid() } if parse_error is None: # Get submitted answer, raising an exception if it does not exist a_sub = data['submitted_answers'].get(name, None) if a_sub is None: raise Exception('submitted answer is None') # If answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_sub = pl.from_json(a_sub) html_params['suffix'] = suffix html_params['a_sub'] = '{:.12g}'.format(a_sub) else: raw_submitted_answer = data['raw_submitted_answers'].get( name, None) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape( raw_submitted_answer) # Add true answer to be able to display it in the submitted answer panel ans_true = None if pl.get_boolean_attrib(element, 'show-correct-answer', SHOW_CORRECT_ANSWER_DEFAULT): ans_true = format_true_ans(element, data, name) if ans_true is not None: html_params['a_tru'] = ans_true partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl-number-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': ans_true = format_true_ans(element, data, name) if ans_true is not None: html_params = { 'answer': True, 'label': label, 'a_tru': ans_true, 'suffix': suffix } with open('pl-number-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def render(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') label = pl.get_string_attrib(element, 'label', None) if '_pl_matrix_input_format' in data['submitted_answers']: format_type = data['submitted_answers']['_pl_matrix_input_format'].get(name, 'matlab') else: format_type = 'matlab' if data['panel'] == 'question': editable = data['editable'] raw_submitted_answer = data['raw_submitted_answers'].get(name, None) # Get comparison parameters and info strings comparison = pl.get_string_attrib(element, 'comparison', 'relabs') if comparison == 'relabs': rtol = pl.get_float_attrib(element, 'rtol', 1e-2) atol = pl.get_float_attrib(element, 'atol', 1e-8) if (rtol < 0): raise ValueError('Attribute rtol = {:g} must be non-negative'.format(rtol)) if (atol < 0): raise ValueError('Attribute atol = {:g} must be non-negative'.format(atol)) info_params = {'format': True, 'relabs': True, 'rtol': '{:g}'.format(rtol), 'atol': '{:g}'.format(atol)} elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) if (digits < 0): raise ValueError('Attribute digits = {:d} must be non-negative'.format(digits)) info_params = {'format': True, 'sigfig': True, 'digits': '{:d}'.format(digits), 'comparison_eps': 0.51 * (10**-(digits - 1))} elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) if (digits < 0): raise ValueError('Attribute digits = {:d} must be non-negative'.format(digits)) info_params = {'format': True, 'decdig': True, 'digits': '{:d}'.format(digits), 'comparison_eps': 0.51 * (10**-(digits - 0))} else: raise ValueError('method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) info_params['allow_complex'] = pl.get_boolean_attrib(element, 'allow-complex', False) with open('pl-matrix-input.mustache', 'r', encoding='utf-8') as f: info = chevron.render(f, info_params).strip() with open('pl-matrix-input.mustache', 'r', encoding='utf-8') as f: info_params.pop('format', None) info_params['shortformat'] = True shortinfo = chevron.render(f, info_params).strip() html_params = { 'question': True, 'name': name, 'label': label, 'editable': editable, 'info': info, 'shortinfo': shortinfo, 'uuid': pl.get_uuid() } partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) with open('pl-matrix-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'submission': parse_error = data['format_errors'].get(name, None) html_params = { 'submission': True, 'label': label, 'parse_error': parse_error, 'uuid': pl.get_uuid() } if parse_error is None: # Get submitted answer, raising an exception if it does not exist a_sub = data['submitted_answers'].get(name, None) if a_sub is None: raise Exception('submitted answer is None') # If answer is in a format generated by pl.to_json, convert it # back to a standard type (otherwise, do nothing) a_sub = pl.from_json(a_sub) # Wrap answer in an ndarray (if it's already one, this does nothing) a_sub = np.array(a_sub) # Format answer as a string html_params['a_sub'] = pl.string_from_2darray(a_sub, language=format_type, digits=12, presentation_type='g') else: raw_submitted_answer = data['raw_submitted_answers'].get(name, None) if raw_submitted_answer is not None: html_params['raw_submitted_answer'] = escape(raw_submitted_answer) partial_score = data['partial_scores'].get(name, {'score': None}) score = partial_score.get('score', None) if score is not None: try: score = float(score) if score >= 1: html_params['correct'] = True elif score > 0: html_params['partial'] = math.floor(score * 100) else: html_params['incorrect'] = True except Exception: raise ValueError('invalid score' + score) with open('pl-matrix-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() elif data['panel'] == 'answer': # Get true answer - do nothing if it does not exist a_tru = pl.from_json(data['correct_answers'].get(name, None)) if a_tru is not None: a_tru = np.array(a_tru) # Get comparison parameters comparison = pl.get_string_attrib(element, 'comparison', 'relabs') if comparison == 'relabs': rtol = pl.get_float_attrib(element, 'rtol', 1e-2) atol = pl.get_float_attrib(element, 'atol', 1e-8) # FIXME: render correctly with respect to rtol and atol matlab_data = pl.string_from_2darray(a_tru, language='matlab', digits=12, presentation_type='g') python_data = pl.string_from_2darray(a_tru, language='python', digits=12, presentation_type='g') elif comparison == 'sigfig': digits = pl.get_integer_attrib(element, 'digits', 2) matlab_data = pl.string_from_2darray(a_tru, language='matlab', digits=digits, presentation_type='sigfig') python_data = pl.string_from_2darray(a_tru, language='python', digits=digits, presentation_type='sigfig') elif comparison == 'decdig': digits = pl.get_integer_attrib(element, 'digits', 2) matlab_data = pl.string_from_2darray(a_tru, language='matlab', digits=digits, presentation_type='f') python_data = pl.string_from_2darray(a_tru, language='python', digits=digits, presentation_type='f') else: raise ValueError('method of comparison "%s" is not valid (must be "relabs", "sigfig", or "decdig")' % comparison) html_params = { 'answer': True, 'label': label, 'matlab_data': matlab_data, 'python_data': python_data, 'uuid': pl.get_uuid() } if format_type == 'matlab': html_params['default_is_matlab'] = True else: html_params['default_is_python'] = True with open('pl-matrix-input.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' else: raise Exception('Invalid panel type: %s' % data['panel']) return html
def render(element_html, data): if data['panel'] == 'submission': html_params = { 'submission': True, 'graded': True, 'uuid': pl.get_uuid() } feedback = data['feedback'] html_params['graded'] = bool(feedback) grading_succeeded = bool(feedback.get('succeeded', None)) html_params['grading_succeeded'] = grading_succeeded if not grading_succeeded: html_params['message'] = feedback.get('message', None) else: results = feedback.get('results', None) if grading_succeeded and results: html_params['succeeded'] = bool(results.get('succeeded', None)) html_params['score'] = format( results.get('score', 0) * 100, '.2f').rstrip('0').rstrip('.') html_params['achieved_max_points'] = (results.get('score', 0) >= 1.0) html_params['results_color'] = '#4CAF50' if ( results.get('score', 0) >= 1.0) else '#F44336' html_params['has_message'] = bool(results.get( 'message', False)) html_params['message'] = results.get('message', None) html_params['has_output'] = bool(results.get('output', False)) html_params['output'] = results.get('output', None) html_params['has_message_or_output'] = bool( html_params['has_message'] or html_params['has_output']) results_tests = results.get('tests', None) html_params['has_tests'] = bool(results.get('tests', None)) if results_tests: # Let's not assume that people give us a valid array of tests # If any test is missing either points or max_points, we'll # disable detailed scores for all questions tests_missing_points = False for test in results_tests: if test.get('points', None) is None: tests_missing_points = True if test.get('max_points', None) is None: tests_missing_points = True html_params['tests_missing_points'] = tests_missing_points if not tests_missing_points: html_params['points'] = sum(test['points'] for test in results_tests) html_params['max_points'] = sum( test['max_points'] for test in results_tests) # We need to build a new tests array to massage data a bit tests = [] for index, results_test in enumerate(results_tests): test = {} test['index'] = index test['name'] = results_test.get('name', '') test['has_message'] = bool( results_test.get('message', None)) test['message'] = results_test.get('message', None) test['has_output'] = bool( results_test.get('output', None)) test['output'] = results_test.get('output', None) test['has_description'] = bool( results_test.get('description', None)) test['description'] = results_test.get( 'description', None) if not tests_missing_points: test['max_points'] = results_test.get('max_points') test['points'] = results_test.get('points') correct = test['max_points'] == test['points'] test[ 'results_color'] = '#4CAF50' if correct else '#F44336' test[ 'results_icon'] = 'fa-check' if correct else 'fa-times' tests.append(test) html_params['tests'] = tests with open('pl-external-grader-results.mustache', 'r', encoding='utf-8') as f: html = chevron.render(f, html_params).strip() else: html = '' return html