示例#1
0
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
示例#3
0
    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
示例#4
0
    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]
示例#5
0
 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
示例#9
0
    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]
示例#10
0
    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]
示例#11
0
    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]
示例#12
0
    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]
示例#14
0
    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]
示例#15
0
    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]
示例#16
0
    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]
示例#17
0
    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]
示例#18
0
    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]