예제 #1
0
    def validate_entry_from_allowed(self,
                                    setting_name,
                                    allowed_list,
                                    errorstore,
                                    missing_ok=True):
        """
        Validate that the entry under the setting setting_name for this object is in the allowed list.
        If not, put the error in the errorstore.

        :param setting_name: The name of the setting
        :param allowed_list: List of allowed entries
        :param errorstore: ErrorStore object to store errors
        :param missing_ok: Whether the entry can be missing
        :return: None
        """
        # Check the entry
        entry = self.attributes.get(setting_name)
        if entry is None:
            if not missing_ok:  # pragma: no cover
                msg = f"The tag {self} does not have the required setting '{setting_name}'."
                errorstore.add_error(
                    InvalidSetting(self.filenames[-1], msg=msg))
            return
        elif entry not in allowed_list:
            msg = f"The tag {self} has an invalid setting '{setting_name}={entry}'."
            errorstore.add_error(InvalidSetting(self.filenames[-1], msg=msg))
예제 #2
0
    def require_positive_attempts(self, errorstore):
        """
        Require that the object's "attempts" attribute is positive.

        :param errorstore: ErrorStore to report any errors
        :return: None
        """
        attempts = self.attributes.get("attempts")
        try:
            if attempts and int(attempts) < 1:
                msg = f"The tag {self} should have a positive number of attempts."
                errorstore.add_error(
                    InvalidSetting(self.filenames[-1], msg=msg))
        except ValueError:
            msg = f"The tag {self} should have a positive number of attempts."
            errorstore.add_error(InvalidSetting(self.filenames[-1], msg=msg))
예제 #3
0
    def validate(self, course, errorstore):
        """
        Perform validation on this object.

        :param course: The course object, which may contain settings relevant to the validation of this object
        :param errorstore: An ErrorStore object to which errors should be reported
        :return: None
        """
        self.validate_entry_from_allowed("rerandomize", randomize_list,
                                         errorstore)
        self.validate_entry_from_allowed("show_correctness",
                                         show_correctness_list, errorstore)
        self.validate_entry_from_allowed("showanswer", show_answer_list,
                                         errorstore)

        # Clean the start and due dates
        self.clean_date("start", errorstore)
        self.clean_date("due", errorstore)

        # Ensure dates fall in the correct order
        self.ensure_date_order(
            course.attributes.get("start"),
            self.attributes.get("start"),
            errorstore,
            same_ok=True,
            error_msg="start date cannot be before course start date")
        self.ensure_date_order(
            self.attributes.get("start"),
            course.attributes.get("end"),
            errorstore,
            error_msg="start date must be before course end date")
        self.ensure_date_order(
            course.attributes.get("start"),
            self.attributes.get("due"),
            errorstore,
            error_msg="due date must be after course start date")
        self.ensure_date_order(self.attributes.get("start"),
                               self.attributes.get("due"),
                               errorstore,
                               error_msg="start date must be before due date")
        self.ensure_date_order(
            self.attributes.get("due"),
            course.attributes.get("end"),
            errorstore,
            same_ok=True,
            error_msg="due date must be before course end date")

        # If this is a timed exam, make sure it's enabled in the policy
        if self.is_exam:
            if not course.attributes.get('enable_timed_exams'):
                msg = f"The tag {self} is a timed exam, but the course policy does not have 'enable_timed_exams=true'."
                errorstore.add_error(
                    InvalidSetting(self.filenames[-1], msg=msg))
예제 #4
0
    def validate(self, course, errorstore):
        """
        Perform validation on this object.

        :param course: The course object, which may contain settings relevant to the validation of this object
        :param errorstore: An ErrorStore object to which errors should be reported
        :return: None
        """
        # Require some settings that need no further validation
        self.require_setting("org", errorstore)
        self.require_setting("course", errorstore)

        # Validate settings from the allowed list
        self.validate_entry_from_allowed("rerandomize", randomize_list,
                                         errorstore)
        self.validate_entry_from_allowed("show_correctness",
                                         show_correctness_list, errorstore)
        self.validate_entry_from_allowed("showanswer", show_answer_list,
                                         errorstore)

        # Handle start/end dates
        self.clean_date("start", errorstore, required=True)
        self.clean_date("end", errorstore, required=True)
        self.ensure_date_order(self.attributes.get("start"),
                               self.attributes.get("end"),
                               errorstore,
                               error_msg="start date must be before end date")

        # Handle enrollment_start/enrollment_end dates
        self.clean_date("enrollment_start", errorstore)
        self.clean_date("enrollment_end", errorstore)

        # Make sure there's a course image
        self.require_setting("course_image", errorstore)
        if self.attributes.get("course_image"):
            if not check_static_file_exists(
                    self, self.attributes.get("course_image")):
                errorstore.add_error(
                    MissingFile(
                        self.filenames[-1],
                        edxobj=self,
                        missing_file=self.attributes.get("course_image")))

        # Check that the grace period is valid
        if not validate_graceperiod(self.attributes.get("graceperiod")):
            errorstore.add_error(
                InvalidSetting(
                    self.filenames[-1],
                    msg="Unable to recognize graceperiod format in policy."))

        # Ensure that the default number of attempts is None or positive
        self.require_positive_attempts(errorstore)
예제 #5
0
    def require_setting(self, setting_name, errorstore):
        """
        Validate that the setting setting_name has an entry for this object.
        If not, put the error in the errorstore.

        :param setting_name: The name of the setting
        :param errorstore: ErrorStore object to store errors
        :return: None
        """
        # Check the entry
        entry = self.attributes.get(setting_name)
        if entry is None:
            msg = f"The tag {self} does not have the required setting '{setting_name}'."
            errorstore.add_error(InvalidSetting(self.filenames[-1], msg=msg))
예제 #6
0
    def validate(self, course, errorstore):
        """
        Perform validation on this object.

        :param course: The course object, which may contain settings relevant to the validation of this object
        :param errorstore: An ErrorStore object to which errors should be reported
        :return: None
        """
        # The data for this Xblock is exported as json in the data field
        datafields = self.attributes.get('data')
        if datafields is None:
            return

        # Make sure that the field is valid, and save it for future validation
        try:
            parsed_data = json.loads(datafields)
            self.parsed_data = parsed_data
            if not isinstance(self.parsed_data, dict):
                msg = f"The tag {self} data JSON is not a dictionary."
                errorstore.add_error(InvalidSetting(self.filenames[-1], msg=msg))
        except json.decoder.JSONDecodeError as err:
            msg = f"The tag {self} has an error in the data JSON: {err}."
            errorstore.add_error(InvalidSetting(self.filenames[-1], msg=msg))
            return
예제 #7
0
    def convert2date(self, date, errorstore, setting_name):
        """
        Returns a date interpretation of the given date.
        If there's an issue, store it in the errorstore, using setting_name in the error message.
        """
        if date is None:
            return None

        # Make sure it parses properly
        try:
            parsed_date = dateutil.parser.parse(date)
            if parsed_date.tzinfo is None or parsed_date.tzinfo.utcoffset(
                    parsed_date) is None:
                # Apply a timezone to the date
                return pytz.utc.localize(parsed_date)
            else:
                # Shift to UTC
                return parsed_date.astimezone(pytz.utc)
        except (TypeError, ValueError):
            msg = f"The tag {self} has an invalid date setting for {setting_name}: '{date}'."
            errorstore.add_error(InvalidSetting(self.filenames[-1], msg=msg))
            return None
예제 #8
0
    def validate(self, course, errorstore):
        """
        Perform validation on this object.

        :param course: The course object, which may contain settings relevant to the validation of this object
        :param errorstore: An ErrorStore object to which errors should be reported
        :return: None
        """
        self.validate_entry_from_allowed("rerandomize", randomize_list,
                                         errorstore)
        self.validate_entry_from_allowed("show_correctness",
                                         show_correctness_list, errorstore)
        self.validate_entry_from_allowed("showanswer", show_answer_list,
                                         errorstore)

        # Clean the start and due dates
        self.clean_date("start", errorstore)
        self.clean_date("due", errorstore)

        # Ensure dates fall in the correct order
        self.ensure_date_order(
            course.attributes.get("start"),
            self.attributes.get("start"),
            errorstore,
            same_ok=True,
            error_msg="start date cannot be before course start date")
        self.ensure_date_order(
            self.attributes.get("start"),
            course.attributes.get("end"),
            errorstore,
            error_msg="start date must be before course end date")
        self.ensure_date_order(
            course.attributes.get("start"),
            self.attributes.get("due"),
            errorstore,
            error_msg="due date must be after course start date")
        self.ensure_date_order(self.attributes.get("start"),
                               self.attributes.get("due"),
                               errorstore,
                               error_msg="start date must be before due date")
        self.ensure_date_order(
            self.attributes.get("due"),
            course.attributes.get("end"),
            errorstore,
            same_ok=True,
            error_msg="due date must be before course end date")

        # Ensure that problem weight isn't negative
        weight = self.attributes.get("weight")
        if weight and float(weight) < 0:
            msg = f"The tag {self} has a negative problem weight."
            errorstore.add_error(InvalidSetting(self.filenames[-1], msg=msg))

        # Ensure that number of attempts is None or positive
        self.require_positive_attempts(errorstore)

        # Detect scripts
        self.scripts = self.detect_scripts()

        # Detect response types
        self.response_types = self.detect_response_types()

        # Detect input types
        self.input_types = self.detect_input_types()

        # Detect solution
        self.has_solution = self.detect_solution()