def depart_html(self, node): ''' The HTML render ends for the node. ''' node._body_children_end = len(self.body) self.body.append(node.endtag()) node._body_end = len(self.body) if hasattr(node, 'html_extract'): node._html = u"".join(self.body[(node._body_begin + 1):-1]) if hasattr(node, 'yaml_data'): recursive_fill(self.body, node.yaml_data, node) if 'data-aplus-exercise' in node: # If the body of the submit directive is used to define the exercise # description in RST, store the exercise description in the HTML format # into the exercise YAML configuration file. # If the submit directive is used without a body, it contains # a placeholder text node, in which case the instructions in YAML # must not be modified. has_body = not (len(node) > 0 and node[0].tagname == 'p' and len(node[0]) > 0 \ and node[0][0].astext() == translations.get(self.builder.env, 'submit_placeholder')) if has_body: # The instructions in the submit directive body have been # compiled to HTML at this point. # Drop the wrapper <div data-aplus-exercise> element. node.yaml_data['instructions'] = ''.join( self.body[node._body_begin + 1:node._body_end - 1]) if hasattr(node, 'yaml_write'): yaml_writer.write(node.yaml_write, node.pop_yaml()) if node.no_write: self.body = node._real_body
def create_question(self, title_text=None, points=True): env = self.state.document.settings.env env.question_count += 1 # Create base element and data. node = aplus_nodes.html('div', { 'class': ' '.join(self.get_classes()), }) data = { 'type': self.grader_field_type(), 'extra_info': self.get_extra_info(), } key = self.options.get('key', None) if key: data['key'] = key # Add title. if not title_text is None: data['title'] = title_text elif env.questionnaire_is_feedback: data['title'] = title_text = '' else: # "#" in the question title is converted to a number in the MOOC-grader. # The questions of a "pick randomly" questionnaire should be numbered # in the MOOC-grader since they are randomly selected. postfix = '{#}' if env.aplus_pick_randomly_quiz else "{:d}".format( env.question_count) data['title|i18n'] = translations.opt('question', postfix=postfix) title_text = "{} {:d}".format(translations.get(env, 'question'), env.question_count) if title_text: title = aplus_nodes.html('label', {}) title.append(nodes.Text(title_text)) node.append(title) # Add configuration. if points and len(self.arguments) > 0: question_points = int(self.arguments[0]) data['points'] = question_points env.aplus_quiz_total_points += question_points if env.aplus_pick_randomly_quiz: if env.aplus_single_question_points is None: env.aplus_single_question_points = question_points else: if env.aplus_single_question_points != question_points: source, line = self.state_machine.get_source_and_line( self.lineno) logger.warning( "Each question must have equal points when " "the questionnaire uses the 'pick randomly' option.", location=(source, line)) if 'required' in self.options: data['required'] = True node.set_yaml(data, 'question') return env, node, data
def create_question(self, title_text=None, points=True): env = self.state.document.settings.env env.question_count += 1 # Create base element and data. node = aplus_nodes.html(u'div', { u'class': u' '.join(self.get_classes()), }) data = { u'type': self.grader_field_type(), u'extra_info': self.get_extra_info(), } key = self.options.get('key', None) if key: data[u'key'] = yaml_writer.ensure_unicode(key) # Add title. if not title_text is None: data[u'title'] = title_text elif env.questionnaire_is_feedback: data[u'title'] = title_text = u'' else: data[u'title|i18n'] = translations.opt('question', postfix=u" {:d}".format( env.question_count)) title_text = u"{} {:d}".format(translations.get(env, 'question'), env.question_count) if title_text: title = aplus_nodes.html(u'label', {}) title.append(nodes.Text(title_text)) node.append(title) # Add configuration. if points and len(self.arguments) > 0: question_points = int(self.arguments[0]) data['points'] = question_points env.aplus_quiz_total_points += question_points if env.aplus_pick_randomly_quiz: if env.aplus_single_question_points is None: env.aplus_single_question_points = question_points else: if env.aplus_single_question_points != question_points: source, line = self.state_machine.get_source_and_line( self.lineno) logger.warning( "Each question must have equal points when " "the questionnaire uses the 'pick randomly' option.", location=(source, line)) if 'required' in self.options: data[u'required'] = True node.set_yaml(data, u'question') return env, node, data
def run(self): if len(self.arguments) > 0: key = self.arguments[0] else: raise SphinxError('Missing active element input id') env = self.state.document.settings.env name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key) override = env.config.override classes = [] if 'class' in self.options: classes.extend(self.options['class']) # Add document nodes. args = { u'class': u' '.join(classes), u'data-aplus-active-element': u'in', u'id': u'' + key, } if 'title' in self.options: args['data-title'] = self.options['title'] if 'default' in self.options: args['data-default'] = self.options['default'] if 'type' in self.options: args['data-type'] = self.options['type'] if 'width' in self.options: args['style'] = 'width:' + self.options['width'] + ';' if 'height' in self.options: if 'style' not in args: args['style'] = 'height:' + self.options['height'] + ';' else: args['style'] = args['style'] + 'height:' + self.options[ 'height'] + ';' if 'clear' in self.options: args['style'] = args['style'] + 'clear:' + self.options[ 'clear'] + ';' node = aplus_nodes.html(u'div', args) paragraph = aplus_nodes.html(u'p', {}) paragraph.append( nodes.Text(translations.get(env, 'active_element_placeholder'))) node.append(paragraph) return [node]
def generate_options(self, env, node): options = [] for i, key in enumerate(['agreement4', 'agreement3', 'agreement2', 'agreement1', 'agreement0']): options.append({ u'value': 4 - i, u'label|i18n': translations.opt(key), }) choice = aplus_nodes.html(u'div', {u'class':u'radio'}) label = aplus_nodes.html(u'label', {}) label.append(aplus_nodes.html(u'input', { u'type': u'radio', u'name': u'field_{:d}'.format(env.question_count - 1), u'value': 4 - i, })) label.append(nodes.Text(translations.get(env, key))) choice.append(label) node.append(choice) return options
def generate_options(self, env, node): options = [] for i, key in enumerate(['agreement4', 'agreement3', 'agreement2', 'agreement1', 'agreement0']): options.append({ u'value': 4 - i, u'label|i18n': translations.opt(key), }) choice = aplus_nodes.html(u'div', {u'class':u'radio'}) label = aplus_nodes.html(u'label', {}) label.append(aplus_nodes.html(u'input', { u'type': u'radio', u'name': u'field_{:d}'.format(env.question_count - 1), u'value': 4 - i, })) label.append(nodes.Text(translations.get(env, key))) choice.append(label) node.append(choice) return options
def create_question(self, title_text=None, points=True): env = self.state.document.settings.env env.question_count += 1 # Create base element and data. node = aplus_nodes.html(u'div', { u'class': u' '.join(self.get_classes()), }) data = { u'type': self.grader_field_type(), u'extra_info': self.get_extra_info(), } key = self.options.get('key', None) if key: data[u'key'] = yaml_writer.ensure_unicode(key) # Add title. if not title_text is None: data[u'title'] = title_text elif env.questionnaire_is_feedback: data[u'title'] = title_text = u'' else: data[u'title|i18n'] = translations.opt('question', postfix=u" {:d}".format( env.question_count)) title_text = u"{} {:d}".format(translations.get(env, 'question'), env.question_count) if title_text: title = aplus_nodes.html(u'label', {}) title.append(nodes.Text(title_text)) node.append(title) # Add configuration. if points and len(self.arguments) > 0: env.aplus_quiz_total_points += int(self.arguments[0]) data[u'points'] = int(self.arguments[0]) if 'required' in self.options: data[u'required'] = True node.set_yaml(data, u'question') return env, node, data
def create_question(self, title_text=None, points=True): env = self.state.document.settings.env env.question_count += 1 # Create base element and data. node = aplus_nodes.html(u'div', { u'class': u' '.join(self.get_classes()), }) data = { u'type': self.grader_field_type(), u'extra_info': self.get_extra_info(), } key = self.options.get('key', None) if key: data[u'key'] = yaml_writer.ensure_unicode(key) # Add title. if not title_text is None: data[u'title'] = title_text elif env.questionnaire_is_feedback: data[u'title'] = title_text = u'' else: data[u'title|i18n'] = translations.opt('question', postfix=u" {:d}".format(env.question_count)) title_text = u"{} {:d}".format(translations.get(env, 'question'), env.question_count) if title_text: title = aplus_nodes.html(u'label', {}) title.append(nodes.Text(title_text)) node.append(title) # Add configuration. if points and len(self.arguments) > 0: data[u'points'] = int(self.arguments[0]) if 'required' in self.options: data[u'required'] = True node.set_yaml(data, u'question') return env, node, data
def run(self): if len(self.arguments) > 0: key = self.arguments[0] else: raise SphinxError('Missing active element input id') env = self.state.document.settings.env name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key) override = env.config.override classes = [] if 'class' in self.options: classes.extend(self.options['class']) # Add document nodes. args = { u'class': u' '.join(classes), u'data-aplus-active-element': u'in', u'id': u''+ key, } if 'title' in self.options: args['data-title'] = self.options['title'] if 'default' in self.options: args['data-default'] = self.options['default'] if 'type' in self.options: args['data-type'] = self.options['type'] if 'width' in self.options: args['style'] = 'width:'+ self.options['width'] + ';' if 'height' in self.options: if 'style' not in args: args['style'] = 'height:'+ self.options['height'] + ';' else: args['style'] = args['style'] + 'height:'+ self.options['height'] + ';' if 'clear' in self.options: args['style'] = args['style'] + 'clear:'+ self.options['clear'] + ';' node = aplus_nodes.html(u'div', args) paragraph = aplus_nodes.html(u'p', {}) paragraph.append(nodes.Text(translations.get(env, 'active_element_placeholder'))) node.append(paragraph) # For clickable inputs the pre-generated html # needs to be added after the regular a+ exercise node if 'type' in self.options and self.options['type'] == 'clickable': if 'file' not in self.options: logger.warning('Clickable active element input "{}" missing template file.' .format(name), location=node) return [node] # Read given html file (from docutils.directives.misc.raw) source_dir = os.path.dirname( os.path.abspath(self.state.document.current_source)) path = os.path.join(env.app.srcdir, self.options['file']) path = utils.relative_path(None, path) try: raw_file = io.FileInput(source_path=path, encoding=self.state.document.settings.input_encoding, error_handler=self.state.document.settings.input_encoding_error_handler) except IOError as error: logger.error(u'Problem with "%s" directive:\n%s.' % (self.name, ErrorString(error)), location=node) return [] try: text = raw_file.read() except UnicodeError as error: logger.error(u'Problem with "%s" directive:\n%s.' % (self.name, ErrorString(error)), location=node) return [] # Generate raw node rawnode = nodes.raw('', text, **{'format':'html'}) wrapnode = aplus_nodes.html(u'div', {u'id': u''+ key + '-wrap',u'class': u'clickable-ae-wrapper'}) wrapnode.append(node) wrapnode.append(rawnode) return [wrapnode] return [node]
def run(self): key, difficulty, points = self.extract_exercise_arguments() env = self.state.document.settings.env name = "{}_{}".format(env.docname.replace('/', '_'), key) override = env.config.override classes = ['exercise'] if 'class' in self.options: classes.extend(self.options['class']) if difficulty: classes.append('difficulty-' + difficulty) # Add document nodes. args = { 'class': ' '.join(classes), 'data-aplus-exercise': 'yes', } if 'quiz' in self.options: args['data-aplus-quiz'] = 'yes' if 'ajax' in self.options: args['data-aplus-ajax'] = 'yes' node = aplus_nodes.html('div', args) key_title = "{} {}".format(translations.get(env, 'exercise'), key) # Load or create exercise configuration. if 'config' in self.options: path = os.path.join(env.app.srcdir, self.options['config']) if not os.path.exists(path): raise SphinxError('Missing config path {}'.format( self.options['config'])) data = yaml_writer.read(path) config_title = data.get('title', '') else: data = {'_external': True} if 'url' in self.options: data['url'] = self.options['url'] if 'lti' in self.options: data.update({ 'lti': self.options['lti'], 'lti_context_id': self.options.get('lti_context_id', ''), 'lti_resource_link_id': self.options.get('lti_resource_link_id', ''), }) if 'lti_aplus_get_and_post' in self.options: data.update({'lti_aplus_get_and_post': True}) if 'lti_open_in_iframe' in self.options: data.update({'lti_open_in_iframe': True}) config_title = '' config_title = self.options.get('title', config_title) if "radar_tokenizer" in self.options or "radar_minimum_match_tokens" in self.options: data['radar_info'] = { 'tokenizer': self.options.get("radar_tokenizer"), 'minimum_match_tokens': self.options.get("radar_minimum_match_tokens"), } category = 'submit' data.update({ 'key': name, 'category': 'submit', 'scale_points': points, 'difficulty': difficulty or '', 'max_submissions': self.options.get( 'submissions', data.get('max_submissions', env.config.program_default_submissions)), 'min_group_size': data.get('min_group_size', env.config.default_min_group_size), 'max_group_size': data.get('max_group_size', env.config.default_max_group_size), 'points_to_pass': self.options.get('points-to-pass', data.get('points_to_pass', 0)), # The RST source file path is needed for fixing relative URLs # in the exercise description. # Replace the Windows path separator backslash \ with the Unix forward slash /. '_rst_srcpath': env.doc2path(env.docname, None).replace('\\', '/'), }) self.set_assistant_permissions(data) if data.get('title|i18n'): # Exercise config.yaml defines title|i18n for multiple languages. # Do not write the field "title" to data in order to avoid conflicts. if config_title: # Overwrite the title for one language since the RST directive # has defined the title option (or alternatively, the yaml file # has "title" in addition to "title|i18n", but that does not make sense). # env.config.language may be incorrect if the language can not be detected. data['title|i18n'][ env.config.language] = env.config.submit_title.format( key_title=key_title, config_title=config_title) else: formatted_title = env.config.submit_title.format( key_title=key_title, config_title=config_title) # If no title has been defined, use key_title as the default. data['title'] = formatted_title if formatted_title else key_title if self.content: self.assert_has_content() # Sphinx can not compile the nested RST into HTML at this stage, hence # the HTML instructions defined in this directive body are added to # the exercise YAML file only at the end of the build. Sphinx calls # the visit functions of the nodes in the last writing phase. # The instructions are added to the YAML file in the depart_html # function in aplus_nodes.py. exercise_description = aplus_nodes.html('div', {}) exercise_description.store_html('exercise_description') nested_parse_with_titles(self.state, self.content, exercise_description) node.append(exercise_description) data['instructions'] = ('#!html', 'exercise_description') else: # The placeholder text is only used in the built HTML # (not in the YAML configurations). paragraph = aplus_nodes.html('p', {}) paragraph.append( nodes.Text(translations.get(env, 'submit_placeholder'))) node.append(paragraph) data.setdefault('status', self.options.get('status', 'unlisted')) source, line = self.state_machine.get_source_and_line(self.lineno) if 'reveal-submission-feedback' in self.options: data['reveal_submission_feedback'] = parse_reveal_rule( self.options['reveal-submission-feedback'], source, line, 'reveal-submission-feedback', ) if 'reveal-model-solutions' in self.options: data['reveal_model_solutions'] = parse_reveal_rule( self.options['reveal-model-solutions'], source, line, 'reveal-model-solutions', ) if 'grading-mode' in self.options: data['grading_mode'] = self.options['grading-mode'] if category in override: data.update(override[category]) if 'url' in data: data['url'] = data['url'].format(key=name) if 'category' in self.options: data['category'] = str(self.options['category']) node.write_yaml(env, name, data, 'exercise') return [node]
def run(self): self.assert_has_content() key, difficulty, points = self.extract_exercise_arguments() # Parse options. classes = ['exercise'] is_feedback = False if 'chapter-feedback' in self.options: classes.append('chapter-feedback') is_feedback = True if 'weekly-feedback' in self.options: classes.append('weekly-feedback') is_feedback = True if 'appendix-feedback' in self.options: classes.append('appendix-feedback') is_feedback = True if 'course-feedback' in self.options: classes.append('course-feedback-questionnaire') is_feedback = True if 'feedback' in self.options: is_feedback = True if is_feedback: key = 'feedback' category = 'feedback' classes.append('feedback') else: category = 'questionnaire' if difficulty: classes.append('difficulty-' + difficulty) if 'category' in self.options: category = str(self.options.get('category')) env = self.state.document.settings.env name = "{}_{}".format(env.docname.replace('/', '_'), key) override = env.config.override env.questionnaire_is_feedback = is_feedback env.question_count = 0 env.aplus_single_question_points = None env.aplus_quiz_total_points = 0 env.aplus_pick_randomly_quiz = 'pick_randomly' in self.options env.aplus_random_question_exists = False # Create document elements. node = aplus_nodes.html('div', { 'class': ' '.join(classes), 'data-aplus-exercise': 'yes', }) form = aplus_nodes.html('form', { 'action': key, 'method': 'post', }) nested_parse_with_titles(self.state, self.content, form) submit = aplus_nodes.html('input', { 'type': 'submit', 'value': translations.get(env, 'submit'), 'class': 'btn btn-primary', }, skip_html=True) form.append(submit) node.append(form) # Write configuration file. data = { 'key': name, 'category': category, 'difficulty': difficulty or '', 'max_submissions': self.options.get( 'submissions', 0 if is_feedback else env.config.questionnaire_default_submissions), 'min_group_size': 1 if is_feedback else env.config.default_min_group_size, 'max_group_size': 1 if is_feedback else env.config.default_max_group_size, 'points_to_pass': self.options.get('points-to-pass', 0), 'feedback': is_feedback, 'view_type': 'access.types.stdsync.createForm', 'status': self.options.get('status', 'unlisted'), 'fieldgroups': [{ 'title': '', 'fields': ('#!children', None), }], # The RST source file path is needed for fixing relative URLs # in the exercise description. # Replace the Windows path separator backslash \ with the Unix forward slash /. '_rst_srcpath': env.doc2path(env.docname, None).replace('\\', '/'), } meta_data = env.metadata[env.app.config.master_doc] # Show the model answer after the last submission. if 'reveal-model-at-max-submissions' in self.options: data['reveal_model_at_max_submissions'] = str_to_bool( self.options['reveal-model-at-max-submissions']) else: default_reveal = str_to_bool( meta_data.get( 'questionnaire-default-reveal-model-at-max-submissions', 'false'), error_msg_prefix=env.app.config.master_doc + " questionnaire-default-reveal-model-at-max-submissions: ") if default_reveal: data['reveal_model_at_max_submissions'] = default_reveal # Show the model answer after the module deadline. if 'show-model' in self.options: data['show_model_answer'] = str_to_bool(self.options['show-model']) else: show_default = str_to_bool( meta_data.get('questionnaire-default-show-model', 'true'), error_msg_prefix=env.app.config.master_doc + " questionnaire-default-show-model: ") if not show_default: data['show_model_answer'] = show_default if env.aplus_pick_randomly_quiz: pick_randomly = self.options.get('pick_randomly', 0) if pick_randomly < 1: source, line = self.state_machine.get_source_and_line( self.lineno) raise SphinxError( source + ": line " + str(line) + "\nNumber of fields to sample randomly should be greater than zero " "(option pick_randomly in the questionnaire directive).") data['fieldgroups'][0]['pick_randomly'] = pick_randomly if 'preserve-questions-between-attempts' in self.options: data['fieldgroups'][0]['resample_after_attempt'] = False elif not env.aplus_random_question_exists: # The HTML attribute data-aplus-quiz makes the A+ frontend show the # questionnaire feedback in place of the exercise description once # the student has submitted at least once. In randomized questionnaires, # the same form may not be submitted again due to one-time use nonce # values, hence the attribute must not be used in randomized # questionnaires. node.attributes['data-aplus-quiz'] = 'yes' if 'autosave' in self.options or env.config.enable_autosave: node.attributes['data-aplus-autosave'] = 'yes' self.set_assistant_permissions(data) points_set_in_arguments = len( self.arguments) == 2 and difficulty != self.arguments[1] if env.aplus_pick_randomly_quiz: calculated_max_points = (self.options.get('pick_randomly') * env.aplus_single_question_points if env.aplus_single_question_points is not None else 0) else: calculated_max_points = env.aplus_quiz_total_points if calculated_max_points == 0 and is_feedback: data['max_points'] = points else: if points_set_in_arguments and calculated_max_points != points: source, line = self.state_machine.get_source_and_line( self.lineno) raise SphinxError( source + ": line " + str(line) + "\nThe points of the questions in the questionnaire must add up to the total points of the questionnaire!" ) data['max_points'] = calculated_max_points if 'title' in self.options: data['title'] = self.options.get('title') else: data['title|i18n'] = translations.opt( 'feedback') if is_feedback else translations.opt( 'exercise', postfix=" {}".format(key)) source, line = self.state_machine.get_source_and_line(self.lineno) if 'reveal-submission-feedback' in self.options: data['reveal_submission_feedback'] = parse_reveal_rule( self.options['reveal-submission-feedback'], source, line, 'reveal-submission-feedback', ) if 'reveal-model-solutions' in self.options: data['reveal_model_solutions'] = parse_reveal_rule( self.options['reveal-model-solutions'], source, line, 'reveal-model-solutions', ) if 'grading-mode' in self.options: data['grading_mode'] = self.options['grading-mode'] if not 'no-override' in self.options and category in override: data.update(override[category]) if 'url' in data: data['url'] = data['url'].format(key=name) form.write_yaml(env, name, data, 'exercise') return [node]
def run(self): key, difficulty, points = self.extract_exercise_arguments() env = self.state.document.settings.env name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key) override = env.config.override classes = [u'exercise'] if 'class' in self.options: classes.extend(self.options['class']) if difficulty: classes.append(u'difficulty-' + difficulty) # Add document nodes. args = { u'class': u' '.join(classes), u'data-aplus-exercise': u'yes', } if 'quiz' in self.options: args[u'data-aplus-quiz'] = u'yes' if 'ajax' in self.options: args[u'data-aplus-ajax'] = u'yes' node = aplus_nodes.html(u'div', args) paragraph = aplus_nodes.html(u'p', {}) paragraph.append( nodes.Text(translations.get(env, 'submit_placeholder'))) node.append(paragraph) key_title = u"{} {}".format(translations.get(env, 'exercise'), key) # Load or create exercise configuration. if 'config' in self.options: path = os.path.join(env.app.srcdir, self.options['config']) if not os.path.exists(path): raise SphinxError('Missing config path {}'.format( self.options['config'])) data = yaml_writer.read(path) config_title = data.get(u'title', None) else: data = {u'_external': True} if 'url' in self.options: data[u'url'] = ensure_unicode(self.options['url']) if 'lti' in self.options: data.update({ u'lti': ensure_unicode(self.options['lti']), u'lti_context_id': ensure_unicode(self.options.get('lti_context_id', u'')), u'lti_resource_link_id': ensure_unicode( self.options.get('lti_resource_link_id', u'')), }) config_title = None config_title = self.options.get('title', config_title) if "radar_tokenizer" in self.options or "radar_minimum_match_tokens" in self.options: data[u'radar_info'] = { u'tokenizer': self.options.get("radar_tokenizer"), u'minimum_match_tokens': self.options.get("radar_minimum_match_tokens"), } category = u'submit' data.update({ u'key': name, u'title': env.config.submit_title.format(key_title=key_title, config_title=config_title), u'category': u'submit', u'scale_points': points, u'difficulty': difficulty or '', u'max_submissions': self.options.get( 'submissions', data.get('max_submissions', env.config.program_default_submissions)), u'min_group_size': data.get('min_group_size', env.config.default_min_group_size), u'max_group_size': data.get('max_group_size', env.config.default_max_group_size), u'points_to_pass': self.options.get('points-to-pass', data.get('points_to_pass', 0)), }) if category in override: data.update(override[category]) if 'url' in data: data['url'] = data['url'].format(key=name) if 'category' in self.options: data['category'] = str(self.options['category']) node.write_yaml(env, name, data, 'exercise') return [node]
def run(self): self.assert_has_content() key, difficulty, points = self.extract_exercise_arguments() # Parse options. classes = [u'exercise'] is_feedback = False if 'chapter-feedback' in self.options: classes.append(u'chapter-feedback') is_feedback = True if 'weekly-feedback' in self.options: classes.append(u'weekly-feedback') is_feedback = True if 'appendix-feedback' in self.options: classes.append(u'appendix-feedback') is_feedback = True if 'course-feedback' in self.options: classes.append(u'course-feedback-questionnaire') is_feedback = True if 'feedback' in self.options: is_feedback = True if is_feedback: key = u'feedback' category = u'feedback' classes.append(u'feedback') else: category = u'questionnaire' if difficulty: classes.append(u'difficulty-' + difficulty) if 'category' in self.options: category = str(self.options.get('category')) env = self.state.document.settings.env name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key) override = env.config.override env.questionnaire_is_feedback = is_feedback env.question_count = 0 # Create document elements. node = aplus_nodes.html(u'div', { u'class': u' '.join(classes), u'data-aplus-exercise': u'yes', u'data-aplus-quiz': u'yes', }) form = aplus_nodes.html(u'form', { u'action': key, u'method': u'post', }) nested_parse_with_titles(self.state, self.content, form) submit = aplus_nodes.html(u'input', { u'type': u'submit', u'value': translations.get(env, u'submit'), u'class': u'btn btn-primary', }, skip_html=True) form.append(submit) node.append(form) # Write configuration file. data = { u'key': name, u'category': category, u'max_points': points, u'difficulty': difficulty or '', u'max_submissions': self.options.get('submissions', 0 if is_feedback else env.config.questionnaire_default_submissions), u'min_group_size': 1 if is_feedback else env.config.default_min_group_size, u'max_group_size': 1 if is_feedback else env.config.default_max_group_size, u'points_to_pass': self.options.get('points-to-pass', 0), u'feedback': is_feedback, u'view_type': u'access.types.stdsync.createForm', u'title|i18n': translations.opt('feedback') if is_feedback else translations.opt('exercise', postfix=u" {}".format(key)), u'fieldgroups': [{ u'title': '', u'fields': (u'#!children', None), }], } if not 'no-override' in self.options and category in override: data.update(override[category]) if 'url' in data: data['url'] = data['url'].format(key=name) if "pick_randomly" in self.options: pick_randomly = self.options.get('pick_randomly', 0) if pick_randomly < 1: raise SphinxError(u'Number of fields to sample randomly should greater than zero.') data[u'fieldgroups'][0]['pick_randomly'] = pick_randomly form.write_yaml(env, name, data, 'exercise') return [node]
def run(self): key, difficulty, points = self.extract_exercise_arguments() env = self.state.document.settings.env name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key) override = env.config.override classes = [u'exercise'] if 'class' in self.options: classes.extend(self.options['class']) if difficulty: classes.append(u'difficulty-' + difficulty) # Add document nodes. args = { u'class': u' '.join(classes), u'data-aplus-exercise': u'yes', } if 'quiz' in self.options: args[u'data-aplus-quiz'] = u'yes' if 'ajax' in self.options: args[u'data-aplus-ajax'] = u'yes' node = aplus_nodes.html(u'div', args) paragraph = aplus_nodes.html(u'p', {}) paragraph.append(nodes.Text(translations.get(env, 'submit_placeholder'))) node.append(paragraph) key_title = u"{} {}".format(translations.get(env, 'exercise'), key) # Load or create exercise configuration. if 'config' in self.options: path = os.path.join(env.app.srcdir, self.options['config']) if not os.path.exists(path): raise SphinxError('Missing config path {}'.format(self.options['config'])) data = yaml_writer.read(path) config_title = data.get(u'title', None) else: data = { u'_external': True } if 'url' in self.options: data[u'url'] = ensure_unicode(self.options['url']) if 'lti' in self.options: data.update({ u'lti': ensure_unicode(self.options['lti']), u'lti_context_id': ensure_unicode(self.options.get('lti_context_id', u'')), u'lti_resource_link_id': ensure_unicode(self.options.get('lti_resource_link_id', u'')), }) config_title = None config_title = self.options.get('title', config_title) if "radar_tokenizer" in self.options or "radar_minimum_match_tokens" in self.options: data[u'radar_info'] = { u'tokenizer': self.options.get("radar_tokenizer"), u'minimum_match_tokens': self.options.get("radar_minimum_match_tokens"), } category = u'submit' data.update({ u'key': name, u'title': env.config.submit_title.format( key_title=key_title, config_title=config_title ), u'category': u'submit', u'scale_points': points, u'difficulty': difficulty or '', u'max_submissions': self.options.get('submissions', data.get('max_submissions', env.config.program_default_submissions)), u'min_group_size': data.get('min_group_size', env.config.default_min_group_size), u'max_group_size': data.get('max_group_size', env.config.default_max_group_size), u'points_to_pass': self.options.get('points-to-pass', data.get('points_to_pass', 0)), }) if category in override: data.update(override[category]) if 'url' in data: data['url'] = data['url'].format(key=name) if 'category' in self.options: data['category'] = str(self.options['category']) node.write_yaml(env, name, data, 'exercise') return [node]
def run(self): if len(self.arguments) > 0: key = self.arguments[0] else: raise SphinxError('Missing active element input id') env = self.state.document.settings.env name = "{}_{}".format(env.docname.replace('/', '_'), key) override = env.config.override classes = [] if 'class' in self.options: classes.extend(self.options['class']) # Add document nodes. args = { 'class': ' '.join(classes), 'data-aplus-active-element': 'in', 'id': '' + key, } if 'title' in self.options: args['data-title'] = self.options['title'] if 'default' in self.options: args['data-default'] = self.options['default'] if 'type' in self.options: args['data-type'] = self.options['type'] if 'width' in self.options: args['style'] = 'width:' + self.options['width'] + ';' if 'height' in self.options: if 'style' not in args: args['style'] = 'height:' + self.options['height'] + ';' else: args['style'] = args['style'] + 'height:' + self.options[ 'height'] + ';' if 'clear' in self.options: args['style'] = args['style'] + 'clear:' + self.options[ 'clear'] + ';' node = aplus_nodes.html('div', args) paragraph = aplus_nodes.html('p', {}) paragraph.append( nodes.Text(translations.get(env, 'active_element_placeholder'))) node.append(paragraph) # For clickable inputs the pre-generated html # needs to be added after the regular a+ exercise node if 'type' in self.options and self.options['type'] == 'clickable': if 'file' not in self.options: logger.warning( 'Clickable active element input "{}" missing template file.' .format(name), location=node) return [node] # Read given html file (from docutils.directives.misc.raw) source_dir = os.path.dirname( os.path.abspath(self.state.document.current_source)) path = os.path.join(env.app.srcdir, self.options['file']) path = utils.relative_path(None, path) try: raw_file = io.FileInput( source_path=path, encoding=self.state.document.settings.input_encoding, error_handler=self.state.document.settings. input_encoding_error_handler) except IOError as error: logger.error('Problem with "%s" directive:\n%s.' % (self.name, ErrorString(error)), location=node) return [] try: text = raw_file.read() except UnicodeError as error: logger.error('Problem with "%s" directive:\n%s.' % (self.name, ErrorString(error)), location=node) return [] # Generate raw node rawnode = nodes.raw('', text, **{'format': 'html'}) wrapnode = aplus_nodes.html('div', { 'id': '' + key + '-wrap', 'class': 'clickable-ae-wrapper' }) wrapnode.append(node) wrapnode.append(rawnode) return [wrapnode] return [node]
def run(self): key, difficulty, points = self.extract_exercise_arguments() env = self.state.document.settings.env name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key) override = env.config.override classes = [u'exercise'] if 'class' in self.options: classes.extend(self.options['class']) # Add document nodes. args = { u'class': u' '.join(classes), u'data-aplus-exercise': u'yes', u'data-aplus-active-element': u'out', u'data-inputs': u''+ self.options.get('inputs', ''), } if 'inputs' not in self.options: raise self.warning("The input list for output '{:s}' is empty.".format(key)) if 'type' in self.options: args['data-type'] = self.options['type'] else: args['data-type'] = 'text' if 'scale-size' in self.options: args['data-scale'] = '' if 'title' in self.options: args['data-title'] = self.options['title'] if 'width' in self.options: args['style'] = 'width:'+ self.options['width'] + ';' if 'height' in self.options: if 'style' not in args: args['style'] = 'height:'+ self.options['height'] + ';' else: args['style'] = args['style'] + 'height:'+ self.options['height'] + ';' if 'clear' in self.options: args['style'] = args['style'] + 'clear:'+ self.options['clear'] + ';' node = aplus_nodes.html(u'div', args) paragraph = aplus_nodes.html(u'p', {}) paragraph.append(nodes.Text(translations.get(env, 'submit_placeholder'))) node.append(paragraph) key_title = u"{} {}".format(translations.get(env, 'exercise'), key) # Load or create exercise configuration. if 'config' in self.options: path = os.path.join(env.app.srcdir, self.options['config']) if not os.path.exists(path): raise SphinxError('Missing config path {}'.format(self.options['config'])) data = yaml_writer.read(path) config_title = data.get(u'title', None) else: data = { u'_external': True } if 'url' in self.options: data[u'url'] = ensure_unicode(self.options['url']) config_title = None config_title = self.options.get('title', config_title) category = u'submit' data.update({ u'key': name, u'title': env.config.submit_title.format( key_title=key_title, config_title=config_title ), u'category': u'active elements', u'max_submissions': self.options.get('submissions', data.get('max_submissions', env.config.ae_default_submissions)), }) if category in override: data.update(override[category]) if 'url' in data: data['url'] = data['url'].format(key=name) node.write_yaml(env, name, data, 'exercise') return [node]
def run(self): key, difficulty, points = self.extract_exercise_arguments() env = self.state.document.settings.env name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key) override = env.config.override classes = [u'exercise'] if 'class' in self.options: classes.extend(self.options['class']) # Add document nodes. args = { u'class': u' '.join(classes), u'data-aplus-exercise': u'yes', u'data-aplus-active-element': u'out', u'data-inputs': u''+ self.options.get('inputs', ''), } if 'inputs' not in self.options: raise self.warning("The input list for output '{:s}' is empty.".format(key)) if 'type' in self.options: args['data-type'] = self.options['type'] else: args['data-type'] = 'text' if 'scale-size' in self.options: args['data-scale'] = '' if 'title' in self.options: args['data-title'] = self.options['title'] if 'width' in self.options: args['style'] = 'width:'+ self.options['width'] + ';' if 'height' in self.options: if 'style' not in args: args['style'] = 'height:'+ self.options['height'] + ';' else: args['style'] = args['style'] + 'height:'+ self.options['height'] + ';' if 'clear' in self.options: args['style'] = args['style'] + 'clear:'+ self.options['clear'] + ';' node = aplus_nodes.html(u'div', args) paragraph = aplus_nodes.html(u'p', {}) paragraph.append(nodes.Text(translations.get(env, 'submit_placeholder'))) node.append(paragraph) key_title = u"{} {}".format(translations.get(env, 'exercise'), key) # Load or create exercise configuration. if 'config' in self.options: path = os.path.join(env.app.srcdir, self.options['config']) if not os.path.exists(path): raise SphinxError('Missing config path {}'.format(self.options['config'])) data = yaml_writer.read(path) config_title = data.get(u'title', None) else: data = { u'_external': True } if 'url' in self.options: data[u'url'] = ensure_unicode(self.options['url']) config_title = None config_title = self.options.get('title', config_title) category = u'submit' data.update({ u'key': name, u'title': env.config.submit_title.format( key_title=key_title, config_title=config_title ), u'category': u'active elements', u'max_submissions': self.options.get('submissions', data.get('max_submissions', env.config.ae_default_submissions)), }) data.setdefault('status', self.options.get('status', 'unlisted')) if category in override: data.update(override[category]) if 'url' in data: data['url'] = data['url'].format(key=name) node.write_yaml(env, name, data, 'exercise') return [node]
def run(self): self.assert_has_content() key, difficulty, points = self.extract_exercise_arguments() # Parse options. classes = [u'exercise'] is_feedback = False if 'chapter-feedback' in self.options: classes.append(u'chapter-feedback') is_feedback = True if 'weekly-feedback' in self.options: classes.append(u'weekly-feedback') is_feedback = True if 'appendix-feedback' in self.options: classes.append(u'appendix-feedback') is_feedback = True if 'course-feedback' in self.options: classes.append(u'course-feedback-questionnaire') is_feedback = True if 'feedback' in self.options: is_feedback = True if is_feedback: key = u'feedback' category = u'feedback' classes.append(u'feedback') else: category = u'questionnaire' if difficulty: classes.append(u'difficulty-' + difficulty) if 'category' in self.options: category = str(self.options.get('category')) env = self.state.document.settings.env name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key) override = env.config.override env.questionnaire_is_feedback = is_feedback env.question_count = 0 env.aplus_single_question_points = None env.aplus_quiz_total_points = 0 env.aplus_pick_randomly_quiz = 'pick_randomly' in self.options # Create document elements. node = aplus_nodes.html( u'div', { u'class': u' '.join(classes), u'data-aplus-exercise': u'yes', u'data-aplus-quiz': u'yes', }) form = aplus_nodes.html(u'form', { u'action': key, u'method': u'post', }) nested_parse_with_titles(self.state, self.content, form) submit = aplus_nodes.html(u'input', { u'type': u'submit', u'value': translations.get(env, u'submit'), u'class': u'btn btn-primary', }, skip_html=True) form.append(submit) node.append(form) # Write configuration file. data = { u'key': name, u'category': category, u'difficulty': difficulty or '', u'max_submissions': self.options.get( 'submissions', 0 if is_feedback else env.config.questionnaire_default_submissions), u'min_group_size': 1 if is_feedback else env.config.default_min_group_size, u'max_group_size': 1 if is_feedback else env.config.default_max_group_size, u'points_to_pass': self.options.get('points-to-pass', 0), u'feedback': is_feedback, u'view_type': u'access.types.stdsync.createForm', u'status': self.options.get('status', 'unlisted'), u'fieldgroups': [{ u'title': '', u'fields': (u'#!children', None), }], } self.set_assistant_permissions(data) points_set_in_arguments = len( self.arguments) == 2 and difficulty != self.arguments[1] if 'pick_randomly' in self.options: calculated_max_points = (self.options.get('pick_randomly') * env.aplus_single_question_points if env.aplus_single_question_points is not None else 0) else: calculated_max_points = env.aplus_quiz_total_points if calculated_max_points == 0 and is_feedback: data['max_points'] = points else: if points_set_in_arguments and calculated_max_points != points: source, line = self.state_machine.get_source_and_line( self.lineno) raise SphinxError( source + ": line " + str(line) + "\nThe points of the questions in the questionnaire must add up to the total points of the questionnaire!" ) data['max_points'] = calculated_max_points if 'title' in self.options: data['title'] = self.options.get('title') else: data[u'title|i18n'] = translations.opt( 'feedback') if is_feedback else translations.opt( 'exercise', postfix=u" {}".format(key)) if not 'no-override' in self.options and category in override: data.update(override[category]) if 'url' in data: data['url'] = data['url'].format(key=name) if "pick_randomly" in self.options: pick_randomly = self.options.get('pick_randomly', 0) if pick_randomly < 1: raise SphinxError( u'Number of fields to sample randomly should greater than zero.' ) data[u'fieldgroups'][0]['pick_randomly'] = pick_randomly form.write_yaml(env, name, data, 'exercise') return [node]
def run(self): key, difficulty, points = self.extract_exercise_arguments() env = self.state.document.settings.env name = u"{}_{}".format(env.docname.replace(u'/', u'_'), key) override = env.config.override classes = [u'exercise'] if 'class' in self.options: classes.extend(self.options['class']) if difficulty: classes.append(u'difficulty-' + difficulty) # Add document nodes. args = { u'class': u' '.join(classes), u'data-aplus-exercise': u'yes', } if 'quiz' in self.options: args[u'data-aplus-quiz'] = u'yes' if 'ajax' in self.options: args[u'data-aplus-ajax'] = u'yes' node = aplus_nodes.html(u'div', args) key_title = u"{} {}".format(translations.get(env, 'exercise'), key) # Load or create exercise configuration. if 'config' in self.options: path = os.path.join(env.app.srcdir, self.options['config']) if not os.path.exists(path): raise SphinxError('Missing config path {}'.format(self.options['config'])) data = yaml_writer.read(path) config_title = data.get(u'title', None) else: data = { u'_external': True } if 'url' in self.options: data[u'url'] = ensure_unicode(self.options['url']) if 'lti' in self.options: data.update({ u'lti': ensure_unicode(self.options['lti']), u'lti_context_id': ensure_unicode(self.options.get('lti_context_id', u'')), u'lti_resource_link_id': ensure_unicode(self.options.get('lti_resource_link_id', u'')), }) if 'lti_aplus_get_and_post' in self.options: data.update({u'lti_aplus_get_and_post': True}) if 'lti_open_in_iframe' in self.options: data.update({u'lti_open_in_iframe': True}) config_title = None config_title = self.options.get('title', config_title) if "radar_tokenizer" in self.options or "radar_minimum_match_tokens" in self.options: data[u'radar_info'] = { u'tokenizer': self.options.get("radar_tokenizer"), u'minimum_match_tokens': self.options.get("radar_minimum_match_tokens"), } category = u'submit' data.update({ u'key': name, u'title': env.config.submit_title.format( key_title=key_title, config_title=config_title ), u'category': u'submit', u'scale_points': points, u'difficulty': difficulty or '', u'max_submissions': self.options.get('submissions', data.get('max_submissions', env.config.program_default_submissions)), u'min_group_size': data.get('min_group_size', env.config.default_min_group_size), u'max_group_size': data.get('max_group_size', env.config.default_max_group_size), u'points_to_pass': self.options.get('points-to-pass', data.get('points_to_pass', 0)), }) self.set_assistant_permissions(data) if self.content: self.assert_has_content() nested_parse_with_titles(self.state, self.content, node) # Sphinx can not compile the nested RST into HTML at this stage, hence # the HTML instructions defined in this directive body are added to # the exercise YAML file only at the end of the build. Sphinx calls # the visit functions of the nodes in the last writing phase. # The instructions are added to the YAML file in the depart_html # function in aplus_nodes.py. else: paragraph = aplus_nodes.html(u'p', {}) paragraph.append(nodes.Text(translations.get(env, 'submit_placeholder'))) node.append(paragraph) data.setdefault('status', self.options.get('status', 'unlisted')) if category in override: data.update(override[category]) if 'url' in data: data['url'] = data['url'].format(key=name) if 'category' in self.options: data['category'] = str(self.options['category']) node.write_yaml(env, name, data, 'exercise') return [node]