Beispiel #1
0
    def __init__(self, vctx, location, page_desc):
        super(ChoiceQuestion, self).__init__(vctx, location, page_desc)

        correct_choice_count = 0
        for choice_idx, choice in enumerate(page_desc.choices):
            try:
                choice = str(choice)
            except:
                raise ValidationError(
                    string_concat(
                        "%(location)s, ",
                        _("choice %(idx)d: unable to convert to string")) % {
                            'location': location,
                            'idx': choice_idx + 1
                        })

            if choice.startswith(self.CORRECT_TAG):
                correct_choice_count += 1

            if vctx is not None:
                validate_markup(vctx, location,
                                remove_prefix(self.CORRECT_TAG, choice))

        if correct_choice_count < 1:
            raise ValidationError(
                string_concat(
                    "%(location)s: ",
                    _("one or more correct answer(s) "
                      "expected, %(n_correct)d found")) % {
                          'location': location,
                          'n_correct': correct_choice_count
                      })
Beispiel #2
0
    def __init__(self, vctx, location, page_desc):
        super(FileUploadQuestion, self).__init__(vctx, location, page_desc)

        if not (set(page_desc.mime_types) <= set(self.ALLOWED_MIME_TYPES)):
            raise ValidationError(
                string_concat(
                    location, ": ",
                    _("unrecognized mime types"),
                    " '%(presenttype)s'")
                % {
                    "presenttype": ", ".join(
                        set(page_desc.mime_types)
                        - set(self.ALLOWED_MIME_TYPES))})

        if page_desc.maximum_megabytes <= 0:
            raise ValidationError(
                string_concat(
                    location, ": ",
                    _("'maximum_megabytes' expects a positive value, "
                      "got %(value)s instead")
                    % {"value": str(page_desc.maximum_megabytes)}))

        if vctx is not None:
            if not hasattr(page_desc, "value"):
                vctx.add_warning(location, _("upload question does not have "
                        "assigned point value"))
Beispiel #3
0
    def __init__(self, vctx, location, page_desc):
        super(MultipleChoiceQuestion, self).__init__(vctx, location, page_desc)

        pd = self.page_desc

        if hasattr(pd, "credit_mode"):
            credit_mode = pd.credit_mode

            if (hasattr(pd, "allow_partial_credit")
                    or hasattr(pd, "allow_partial_credit_subset_only")):
                raise ValidationError(
                    string_concat(
                        "%(location)s: ",
                        _("'allow_partial_credit' or "
                          "'allow_partial_credit_subset_only' may not be specified"
                          "at the same time as 'credit_mode'")) %
                    {'location': location})

        else:

            partial = getattr(pd, "allow_partial_credit", False)
            partial_subset = getattr(pd, "allow_partial_credit_subset_only",
                                     False)

            if not partial and not partial_subset:
                credit_mode = "exact"
            elif partial and not partial_subset:
                credit_mode = "proportional"
            elif not partial and partial_subset:
                credit_mode = "proportional_correct"
            else:
                assert partial and partial_subset
                raise ValidationError(
                    string_concat(
                        "%(location)s: ",
                        _("'allow_partial_credit' and "
                          "'allow_partial_credit_subset_only' are not allowed to "
                          "coexist when both attribute are 'True'")) %
                    {'location': location})

        if credit_mode not in [
                "exact", "proportional", "proportional_correct"
        ]:
            raise ValidationError(
                string_concat("%(location)s: ",
                              _("unrecognized credit_mode '%(credit_mode)s'"))
                % {
                    'location': location,
                    "credit_mode": credit_mode
                })

        if vctx is not None and not hasattr(pd, "credit_mode"):
            vctx.add_warning(
                location,
                _("'credit_mode' will be required on multi-select choice "
                  "questions in a future version. set "
                  "'credit_mode: {}' to match current behavior.").format(
                      credit_mode))

        self.credit_mode = credit_mode
Beispiel #4
0
    def __init__(self, vctx, location, page_desc):
        super(ChoiceQuestion, self).__init__(vctx, location, page_desc)

        if self.correct_choice_count < 1:
            raise ValidationError(
                string_concat(
                    "%(location)s: ",
                    _("one or more correct answer(s) "
                      "expected, %(n_correct)d found")) % {
                          'location': location,
                          'n_correct': self.correct_choice_count
                      })

        if self.disregard_choice_count:
            raise ValidationError(
                string_concat(
                    "%(location)s: ",
                    _("ChoiceQuestion does not allow any choices "
                      "marked 'disregard'")) % {'location': location})

        if self.always_correct_choice_count:
            raise ValidationError(
                string_concat(
                    "%(location)s: ",
                    _("ChoiceQuestion does not allow any choices "
                      "marked 'always_correct'")) % {'location': location})
Beispiel #5
0
def get_matcher_class(location, matcher_type, pattern_type):
    for matcher_class in TEXT_ANSWER_MATCHER_CLASSES:
        if matcher_class.type == matcher_type:

            if matcher_class.pattern_type != pattern_type:
                raise ValidationError(
                    string_concat(
                        "%(location)s: ",
                        # Translators: a "matcher" is used to determine
                        # if the answer to text question (blank filling
                        # question) is correct.
                        _("%(matcherclassname)s only accepts "
                            "'%(matchertype)s' patterns"))
                        % {
                            'location': location,
                            'matcherclassname': matcher_class.__name__,
                            'matchertype': matcher_class.pattern_type})

            return matcher_class

    raise ValidationError(
            string_concat(
                "%(location)s: ",
                _("unknown match type '%(matchertype)s'"))
            % {
                'location': location,
                'matchertype': matcher_type})
Beispiel #6
0
    def __init__(self, vctx, location, page_desc):
        super(TextQuestion, self).__init__(vctx, location, page_desc)

        if len(page_desc.answers) == 0:
            raise ValidationError(
                    string_concat(
                        "%s: ",
                        _("at least one answer must be provided"))
                    % location)

        self.matchers = [
                parse_matcher(
                    vctx,
                    "%s, answer %d" % (location, i+1),
                    answer)
                for i, answer in enumerate(page_desc.answers)]

        if not any(matcher.correct_answer_text() is not None
                for matcher in self.matchers):
            raise ValidationError(
                    string_concat(
                        "%s: ",
                        _("no matcher is able to provide a plain-text "
                        "correct answer"))
                    % location)
Beispiel #7
0
    def __init__(self, vctx, location, name, answers_desc):
        super(ShortAnswer, self).__init__(vctx, location, name, answers_desc)

        validate_struct(
            vctx,
            location,
            answers_desc,
            required_attrs=(("type", str), ("correct_answer", list)),
            allowed_attrs=(
                ("weight", (int, float)),
                ("prepended_text", str),
                ("appended_text", str),
                ("hint", str),
                ("hint_title", str),
                ("width", (str, int, float)),
                ("required", bool),
            ),
        )

        self.weight = getattr(answers_desc, "weight", 0)

        if len(answers_desc.correct_answer) == 0:
            raise ValidationError(
                string_concat("%s: ",
                              _("at least one answer must be provided")) %
                location)

        self.hint = getattr(self.answers_desc, "hint", "")
        self.width = getattr(self.answers_desc, "width", None)

        parsed_length = self.get_length_attr_em(location, self.width)

        self.width = 0
        if parsed_length is not None:
            self.width = max(MINIMUN_WIDTH, parsed_length)
        else:
            self.width = DEFAULT_WIDTH

        self.width_str = "width: " + str(self.width) + "em"

        self.matchers = [
            parse_matcher(
                vctx,
                string_concat(
                    "%s, ",
                    # Translators: refers to optional
                    # correct answer for checking
                    # correctness sumbitted by students.
                    _("answer"),
                    " %d") % (location, i + 1),
                answer) for i, answer in enumerate(answers_desc.correct_answer)
        ]

        if not any(matcher.correct_answer_text() is not None
                   for matcher in self.matchers):
            raise ValidationError(
                string_concat(
                    "%s: ",
                    _("no matcher is able to provide a plain-text "
                      "correct answer")) % location)
Beispiel #8
0
    def __init__(self, vctx, location, matcher_desc):
        self.matcher_desc = matcher_desc

        validate_struct(
            vctx,
            location,
            matcher_desc,
            required_attrs=(
                ("type", str),
                ("value", (int, float, str)),
            ),
            allowed_attrs=(
                ("rtol", (int, float, str)),
                ("atol", (int, float, str)),
            ),
        )

        try:
            self.matcher_desc.value = \
                    float_or_sympy_evalf(matcher_desc.value)
        except:
            raise ValidationError(
                string_concat("%s: 'value' ",
                              _("does not provide a valid float literal")) %
                location)

        if hasattr(matcher_desc, "rtol"):
            try:
                self.matcher_desc.rtol = \
                        float_or_sympy_evalf(matcher_desc.rtol)
            except:
                raise ValidationError(
                    string_concat("%s: 'rtol' ",
                                  _("does not provide a valid float literal"))
                    % location)

            if matcher_desc.value == 0:
                raise ValidationError(
                    string_concat("%s: 'rtol' ",
                                  _("not allowed when 'value' is zero")) %
                    location)

        if hasattr(matcher_desc, "atol"):
            try:
                self.matcher_desc.atol = \
                        float_or_sympy_evalf(matcher_desc.atol)
            except:
                raise ValidationError(
                    string_concat("%s: 'atol' ",
                                  _("does not provide a valid float literal"))
                    % location)

        if (not hasattr(matcher_desc, "atol")
                and not hasattr(matcher_desc, "rtol") and vctx is not None):
            vctx.add_warning(
                location,
                _("Float match should have either rtol or atol--"
                  "otherwise it will match any number"))
Beispiel #9
0
    def __init__(self, vctx, location, name, answers_desc):
        super(ChoicesAnswer, self).__init__(
            vctx, location, name, answers_desc)

        validate_struct(
            vctx,
            location,
            answers_desc,
            required_attrs=(
                ("type", str),
                ("choices", list)
                ),
            allowed_attrs=(
                ("weight", (int, float)),
                ("hint", str),
                ("hint_title", str),
                ("required", bool),
                ),
            )

        self.weight = getattr(answers_desc, "weight", 0)

        correct_choice_count = 0
        for choice_idx, choice in enumerate(answers_desc.choices):
            try:
                choice = str(choice)
            except Exception:
                raise ValidationError(
                        string_concat(
                            "%(location)s: '%(answer_name)s' ",
                            _("choice %(idx)d: unable to convert to string")
                            )
                        % {'location': location,
                            'answer_name': self.name,
                            'idx': choice_idx+1})

            if choice.startswith(self.CORRECT_TAG):
                correct_choice_count += 1

            if vctx is not None:
                validate_markup(vctx, location,
                        remove_prefix(self.CORRECT_TAG, choice))

        if correct_choice_count < 1:
            raise ValidationError(
                    string_concat(
                        "%(location)s: ",
                        _("one or more correct answer(s) expected "
                        " for question '%(question_name)s', "
                        "%(n_correct)d found"))
                    % {
                        'location': location,
                        'question_name': self.name,
                        'n_correct': correct_choice_count})

        self.hint = getattr(self.answers_desc, "hint", "")
        self.width = 0
Beispiel #10
0
def parse_validator(vctx, location, validator_desc):
    if not isinstance(validator_desc, Struct):
        raise ValidationError("%s: must be struct or string" % location)

    if not hasattr(validator_desc, "type"):
        raise ValidationError("%s: matcher must supply 'type'" % location)

    return (get_validator_class(location, validator_desc.type)(vctx, location,
                                                               validator_desc))
Beispiel #11
0
    def __init__(self, vctx, location, page_desc):
        super(PythonCodeQuestionWithHumanTextFeedback,
              self).__init__(vctx, location, page_desc)

        if vctx is not None:
            if (hasattr(self.page_desc, "human_feedback_value")
                    and hasattr(self.page_desc, "human_feedback_percentage")):
                raise ValidationError(
                    string_concat(
                        "%(location)s: ",
                        _("'human_feedback_value' and "
                          "'human_feedback_percentage' are not "
                          "allowed to coexist")) % {'location': location})
            if not (hasattr(self.page_desc, "human_feedback_value")
                    or hasattr(self.page_desc, "human_feedback_percentage")):
                raise ValidationError(
                    string_concat(
                        "%(location)s: ",
                        _("expecting either 'human_feedback_value' "
                          "or 'human_feedback_percentage', found neither.")) %
                    {'location': location})
            if hasattr(self.page_desc, "human_feedback_value"):
                vctx.add_warning(
                    location,
                    _("Used deprecated 'human_feedback_value' attribute--"
                      "use 'human_feedback_percentage' instead."))
                if self.page_desc.value == 0:
                    raise ValidationError("".join([
                        "%s: ",
                        _("'human_feedback_value' attribute is not allowed "
                          "if value of question is 0, use "
                          "'human_feedback_percentage' instead")
                    ]) % location)
                if self.page_desc.human_feedback_value > self.page_desc.value:
                    raise ValidationError("".join([
                        "%s: ",
                        _("human_feedback_value greater than overall "
                          "value of question")
                    ]) % location)
            if hasattr(self.page_desc, "human_feedback_percentage"):
                if not (0 <= self.page_desc.human_feedback_percentage <= 100):
                    raise ValidationError("".join([
                        "%s: ",
                        _("the value of human_feedback_percentage "
                          "must be between 0 and 100")
                    ]) % location)

        if hasattr(self.page_desc, "human_feedback_value"):
            self.human_feedback_percentage = (
                self.page_desc.human_feedback_value * 100 /
                self.page_desc.value)
        else:
            self.human_feedback_percentage = (
                self.page_desc.human_feedback_percentage)
Beispiel #12
0
def parse_matcher(vctx, location, matcher_desc):
    if isinstance(matcher_desc, (str, unicode)):
        return parse_matcher_string(vctx, location, matcher_desc)
    else:
        if not isinstance(matcher_desc, Struct):
            raise ValidationError("%s: must be struct or string" % location)

        if not hasattr(matcher_desc, "type"):
            raise ValidationError("%s: matcher must supply 'type'" % location)

        return (get_matcher_class(location, matcher_desc.type,
                                  "struct")(vctx, location, matcher_desc))
Beispiel #13
0
def get_matcher_class(location, matcher_type, pattern_type):
    for matcher_class in TEXT_ANSWER_MATCHER_CLASSES:
        if matcher_class.type == matcher_type:

            if matcher_class.pattern_type != pattern_type:
                raise ValidationError(
                    "%s: %s only accepts '%s' patterns" %
                    (location, matcher_class.__name__, pattern_type))

            return matcher_class

    raise ValidationError("%s: unknown match type '%s'" %
                          (location, matcher_type))
Beispiel #14
0
    def get_length_attr_em(location, width_attr):
        # type: (Text, Text) -> Optional[float]
        """
        generate the length for input box, the unit is 'em'
        """

        if width_attr is None:
            return None

        if isinstance(width_attr, (int, float)):
            return width_attr

        width_re_match = WIDTH_STR_RE.match(width_attr)
        if width_re_match:
            length_value = width_re_match.group(1)
            length_unit = width_re_match.group(2)
        else:
            raise ValidationError(
                string_concat(
                    "%(location)s: ",
                    _("unrecogonized width attribute string: "
                      "'%(width_attr)s'")) % {
                          "location": location,
                          "width_attr": width_attr
                      })

        if length_unit not in ALLOWED_LENGTH_UNIT:
            raise ValidationError(
                string_concat(
                    "%(location)s: ",
                    _(
                        "unsupported length unit '%(length_unit)s', "
                        "expected length unit can be "
                        "%(allowed_length_unit)s", )) %
                {
                    "location":
                    location,
                    "length_unit":
                    length_unit,
                    "allowed_length_unit":
                    ", ".join(
                        ["'" + item + "'" for item in ALLOWED_LENGTH_UNIT])
                })

        if length_unit == "%":
            return float(length_value) * DEFAULT_WIDTH / 100.0
        else:
            return float(length_value) / cast(float, EM_LEN_DICT[length_unit])
Beispiel #15
0
def parse_matcher_string(vctx, location, matcher_desc):
    match = MATCHER_RE.match(matcher_desc)

    if match is not None:
        matcher_type = match.group(1)
        pattern = match.group(2)
    else:
        match = MATCHER_RE_2.match(matcher_desc)

        if match is None:
            raise ValidationError(
                    string_concat(
                        "%s: ",
                        _("does not specify match type"))
                    % location)

        matcher_type = match.group(1)
        pattern = match.group(2)

        if vctx is not None:
            vctx.add_warning(location,
                    _("uses deprecated 'matcher:answer' style"))

    return (get_matcher_class(location, matcher_type, "string")
            (vctx, location, pattern))
Beispiel #16
0
    def __init__(self, vctx, location, page_desc):
        super(PythonCodeQuestion, self).__init__(vctx, location, page_desc)

        if vctx is not None and hasattr(page_desc, "data_files"):
            for data_file in page_desc.data_files:
                try:
                    if not isinstance(data_file, str):
                        raise ObjectDoesNotExist()

                    from course.content import get_repo_blob
                    get_repo_blob(vctx.repo, data_file, vctx.commit_sha)
                except ObjectDoesNotExist:
                    raise ValidationError("%s: data file '%s' not found" %
                                          (location, data_file))

        if not getattr(page_desc, "single_submission",
                       False) and vctx is not None:
            is_multi_submit = False

            if hasattr(page_desc, "access_rules"):
                if hasattr(page_desc.access_rules, "add_permissions"):
                    if (flow_permission.change_answer
                            in page_desc.access_rules.add_permissions):
                        is_multi_submit = True

            if not is_multi_submit:
                vctx.add_warning(
                    location,
                    _("code question does not explicitly "
                      "allow multiple submission. Either add "
                      "access_rules/add_permssions/change_answer "
                      "or add 'single_submission: True' to confirm that you intend "
                      "for only a single submission to be allowed. "
                      "While you're at it, consider adding "
                      "access_rules/add_permssions/see_correctness."))
Beispiel #17
0
    def __init__(self, vctx, location, pattern):
        self.pattern = pattern

        try:
            self.pattern_sym = parse_sympy(pattern)
        except ImportError:
            tp, e, _ = sys.exc_info()
            if vctx is not None:
                vctx.add_warning(
                        location,
                        string_concat(
                            "%(location)s: ",
                            _("unable to check symbolic expression"),
                            "(%(err_type)s: %(err_str)s)")
                        % {
                            'location': location,
                            "err_type": tp.__name__,
                            "err_str": str(e)
                            })

        except Exception:
            tp, e, _ = sys.exc_info()
            raise ValidationError(
                    "%(location)s: %(err_type)s: %(err_str)s"
                    % {
                        "location": location,
                        "err_type": tp.__name__,
                        "err_str": str(e)
                        })
Beispiel #18
0
    def validate(self, new_page_source):
        from relate.utils import dict_to_struct
        import yaml

        try:
            page_desc = dict_to_struct(yaml.safe_load(new_page_source))

            from course.validation import (
                    validate_flow_page, ValidationContext)
            vctx = ValidationContext(
                    # FIXME
                    repo=None,
                    commit_sha=None)

            validate_flow_page(vctx, "submitted page", page_desc)

            if page_desc.type != self.validator_desc.page_type:
                raise ValidationError(ugettext("page must be of type '%s'")
                        % self.validator_desc.page_type)

        except Exception:
            tp, e, _ = sys.exc_info()

            raise forms.ValidationError("%(err_type)s: %(err_str)s"
                    % {"err_type": tp.__name__, "err_str": str(e)})
Beispiel #19
0
    def __init__(self, vctx, location, page_desc):
        super(ChoiceQuestionBase, self).__init__(vctx, location, page_desc)

        self.correct_choice_count = 0
        self.disregard_choice_count = 0
        self.always_correct_choice_count = 0
        for choice_idx, choice in enumerate(page_desc.choices):
            try:
                choice = str(choice)
            except:
                raise ValidationError(
                        string_concat(
                            "%(location)s, ",
                            _("choice %(idx)d: unable to convert to string")
                            )
                        % {'location': location, 'idx': choice_idx+1})

            if choice.startswith(self.CORRECT_TAG):
                self.correct_choice_count += 1

            if choice.startswith(self.DISREGARD_TAG):
                self.disregard_choice_count += 1

            if choice.startswith(self.ALWAYS_CORRECT_TAG):
                self.always_correct_choice_count += 1

            if vctx is not None:
                validate_markup(vctx, location,
                        remove_prefix(self.DISREGARD_TAG,
                            remove_prefix(self.CORRECT_TAG,
                                remove_prefix(self.ALWAYS_CORRECT_TAG,
                                    choice))))
Beispiel #20
0
def parse_matcher(vctx, location, matcher_desc):
    if isinstance(matcher_desc, str):
        return parse_matcher_string(vctx, location, matcher_desc)
    else:
        if not isinstance(matcher_desc, Struct):
            raise ValidationError(
                string_concat("%s: ", _("must be struct or string")) %
                location)

        if not hasattr(matcher_desc, "type"):
            raise ValidationError(
                string_concat("%s: ", _("matcher must supply 'type'")) %
                location)

        return (get_matcher_class(location, matcher_desc.type,
                                  "struct")(vctx, location, matcher_desc))
Beispiel #21
0
def get_validator_class(location, validator_type):
    for validator_class in TEXT_ANSWER_VALIDATOR_CLASSES:
        if validator_class.type == validator_type:
            return validator_class

    raise ValidationError("%s: unknown validator type '%s'" %
                          (location, validator_type))
Beispiel #22
0
    def __init__(self, vctx, location, page_desc):
        super(PageBaseWithTitle, self).__init__(vctx, location, page_desc)

        title = None
        try:
            title = self.page_desc.title
        except AttributeError:
            pass

        if title is None:
            try:
                md_body = self.markup_body_for_title()
            except NotImplementedError:
                from warnings import warn
                warn(_("PageBaseWithTitle subclass '%s' does not implement "
                        "markdown_body_for_title()")
                        % type(self).__name__)
            else:
                title = extract_title_from_markup(md_body)

        if title is None:
            raise ValidationError(
                    string_concat(
                        "%s: ",
                        _("no title found in body or title attribute"))
                    % (location))

        self._title = title
Beispiel #23
0
    def __init__(self, vctx, location, page_desc):
        super(PageBaseWithValue, self).__init__(vctx, location, page_desc)

        if vctx is not None:
            if hasattr(page_desc, "value") and self.is_optional_page:
                raise ValidationError(
                    string_concat(
                        location,
                        _("Attribute 'value' should be removed when "
                          "'is_optional_page' is True.")))

            if hasattr(page_desc, "value") and page_desc.value < 0:
                raise ValidationError(
                    string_concat(
                        location,
                        _("Attribute 'value' expects a non-negative value, "
                          "got %s instead") % str(page_desc.value)))
Beispiel #24
0
def parse_question(vctx, location, name, answers_desc):
    if isinstance(answers_desc, Struct):
        return (get_question_class(location, answers_desc.type,
                                   answers_desc)(vctx, location, name,
                                                 answers_desc))
    else:
        raise ValidationError(
            string_concat("%s: ", _("must be struct")) % location)
Beispiel #25
0
    def __init__(self, vctx, location, pattern):
        try:
            self.pattern = re.compile(pattern, self.re_flags)
        except:
            tp, e, _ = sys.exc_info()

            raise ValidationError("%s: regex '%s' did not compile: %s: %s" %
                                  (location, pattern, tp.__name__, str(e)))
Beispiel #26
0
    def __init__(self, vctx, location, pattern):
        self.pattern = pattern

        try:
            self.pattern_sym = parse_sympy(pattern)
        except:
            tp, e, _ = sys.exc_info()
            raise ValidationError("%s: %s: %s" %
                                  (location, tp.__name__, str(e)))
Beispiel #27
0
    def __init__(self, vctx, location, page_desc):
        super(PythonCodeQuestionWithHumanTextFeedback,
              self).__init__(vctx, location, page_desc)

        if (vctx is not None and
                self.page_desc.human_feedback_value > self.page_desc.value):
            raise ValidationError(
                "%s: human_feedback_value greater than overall "
                "value of question" % location)
Beispiel #28
0
    def __init__(self, vctx, location, page_desc):
        super(TextQuestionBase, self).__init__(vctx, location, page_desc)

        widget = TextAnswerForm.get_text_widget(getattr(
            page_desc, "widget", None),
                                                check_only=True)

        if widget is None:
            raise ValidationError("%s: unrecognized widget type '%s'" %
                                  (location, getattr(page_desc, "widget")))
Beispiel #29
0
    def __init__(self, vctx, location, page_desc):
        super(PageBaseWithValue, self).__init__(vctx, location, page_desc)

        if vctx is not None:
            if hasattr(page_desc, "value") and self.is_optional_page:
                raise ValidationError(
                    string_concat(
                        location,
                        _("Attribute 'value' should be removed when "
                          "'is_optional_page' is True.")))
Beispiel #30
0
    def __init__(self, vctx, location, page_desc):
        super(SurveyChoiceQuestion, self).__init__(vctx, location, page_desc)

        for choice_idx, choice in enumerate(page_desc.choices):
            if not isinstance(choice, six.string_types):
                raise ValidationError("%s, choice %d: not a string" %
                                      (location, choice_idx + 1))

            if vctx is not None:
                validate_markup(vctx, location, choice)