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 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 parse_chapter(docname, doc, parent): for config_file in [ e.yaml_write for e in doc.traverse(aplus_nodes.html) if e.has_yaml(u'exercise') ]: config = yaml_writer.read(config_file) if config.get(u'_external', False): exercise = config.copy() del exercise[u'_external'] else: exercise = { u'key': config[u'key'], u'config': config[u'key'] + u'.yaml', u'max_submissions': config.get(u'max_submissions', 0), u'max_points': config.get(u'max_points', 0), u'difficulty': config.get(u'difficulty', ''), u'points_to_pass': config.get(u'points_to_pass', 0), u'category': config[u'category'], u'min_group_size': config.get(u'min_group_size', 1), u'max_group_size': config.get(u'max_group_size', 1), u'confirm_the_level': config.get(u'confirm_the_level', False), u'allow_assistant_grading': config.get(u'allow_assistant_grading', False), } exercise.update({ u'status': u'unlisted', }) if u'scale_points' in config: exercise[u'max_points'] = config.pop(u'scale_points') parent.append(exercise) if not config[u'category'] in category_keys: category_keys.append(config[u'category']) category = u'chapter' for name, hidden, child in traverse_tocs(app, doc): meta = first_meta(child) status = u'hidden' if 'hidden' in meta else ( u'unlisted' if hidden else u'ready') chapter = { u'key': name.split(u'/')[-1], #name.replace('/', '_'), u'status': status, u'name': first_title(child), u'static_content': name + u'.html', u'category': category, u'use_wide_column': app.config.use_wide_column, u'children': [], } if meta: audience = meta.get('audience') if audience: chapter[u'audience'] = yaml_writer.ensure_unicode(audience) if category in override: chapter.update(override[category]) parent.append(chapter) if not u'chapter' in category_keys: category_keys.append(u'chapter') parse_chapter(name, child, chapter[u'children'])
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']) 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): 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): 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('title', '') 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 = '' 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'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)), # The RST source file path is needed for fixing relative URLs # in the exercise description. '_rst_srcpath': env.doc2path(env.docname, None), }) 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')) 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): 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 parse_chapter(docname, doc, parent): for config_file in [e.yaml_write for e in doc.traverse(aplus_nodes.html) if e.has_yaml(u'exercise')]: config = yaml_writer.read(config_file) if config.get(u'_external', False): exercise = config.copy() del exercise[u'_external'] else: exercise = { u'key': config[u'key'], u'config': config[u'key'] + u'.yaml', u'max_submissions': config.get(u'max_submissions', 0), u'max_points': config.get(u'max_points', 0), u'difficulty': config.get(u'difficulty', ''), u'points_to_pass': config.get(u'points_to_pass', 0), u'category': config[u'category'], u'min_group_size': config.get(u'min_group_size', 1), u'max_group_size': config.get(u'max_group_size', 1), u'confirm_the_level': config.get(u'confirm_the_level', False), } allow_assistant_viewing = config.get(u'allow_assistant_viewing', app.config.allow_assistant_viewing) allow_assistant_grading = config.get(u'allow_assistant_grading', app.config.allow_assistant_grading) exercise.update({ u'status': config.get(u'status', u'unlisted'), u'allow_assistant_viewing': allow_assistant_viewing, u'allow_assistant_grading': allow_assistant_grading, }) if u'scale_points' in config: exercise[u'max_points'] = config.pop(u'scale_points') parent.append(exercise) if not config[u'category'] in category_keys: category_keys.append(config[u'category']) for config_file in [e.yaml_write for e in doc.traverse(aplus_nodes.html) if e.has_yaml(u'exercisecollection')]: config = yaml_writer.read(config_file) exercise = { u'key': config[u'key'], u'max_points': config.get(u'max_points', 0), u'points_to_pass': config.get(u'points_to_pass', 0), u'target_url': config[u'target_url'], u'target_category': config[u'target_category'], u'category': config[u'category'], u'status': config.get(u'status', u'unlisted'), u'title': config[u'title'], } parent.append(exercise) if not config[u'category'] in category_keys: category_keys.append(config[u'category']) category = u'chapter' for name,hidden,child in traverse_tocs(app, doc): meta = first_meta(child) status = u'hidden' if 'hidden' in meta else ( u'unlisted' if hidden else u'ready' ) chapter = { u'status': status, u'name': first_title(child), u'static_content': name + u'.html', u'category': category, u'use_wide_column': app.config.use_wide_column, u'children': [], } # If the chapter RST file is in a nested directory under the module # directory (e.g., module01/material/chapter.rst instead of # module01/chapter.rst), then the chapter key must contain parts of # the nested directory names in order to be unique within the module. # Different directories could contain files with the same names. key_parts = name.split('/') chapter['key'] = '_'.join(key_parts[1:]) if meta: audience = meta.get('audience') if audience: chapter[u'audience'] = yaml_writer.ensure_unicode(audience) if category in override: chapter.update(override[category]) parent.append(chapter) if not u'chapter' in category_keys: category_keys.append(u'chapter') parse_chapter(name, child, chapter[u'children'])
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]