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 })
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"))
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
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})
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})
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)
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)
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"))
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
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))
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)
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))
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))
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])
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))
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."))
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) })
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)})
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))))
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))
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))
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
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)))
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)
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)))
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)))
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)
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")))
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.")))
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)