diff --git a/courses/forms.py b/courses/forms.py index 74583d9eeb..5c82538f2d 100644 --- a/courses/forms.py +++ b/courses/forms.py @@ -141,6 +141,7 @@ def program_requirements_schema(): "format": "number", "title": "Value", "default": 1, + "minimum": 1, "options": { "dependencies": { "node_type": ProgramRequirementNodeType.OPERATOR.value, @@ -269,7 +270,7 @@ def clean(self): # noqa: C901 def _validate_elective_value_presence(operator): """ Verifies that a Program's elective operator contains - a defined Value field. + a defined Value field that is not negative. Args: operator (dict): @@ -286,6 +287,7 @@ def _validate_elective_value_presence(operator): } ValidationError: operator_value does not exist. ValidationError: operator_value does exist but is empty. + ValidationError: operator_value is negative. """ if ( operator["data"]["operator"] @@ -300,6 +302,17 @@ def _validate_elective_value_presence(operator): raise ValidationError( '"Minimum # of" operator must have Value equal to 1 or more.' # noqa: EM101 ) + # Ensure the value is not negative + try: + value = int(operator["data"]["operator_value"]) + if value < 1: + raise ValidationError( + '"Minimum # of" operator must have Value equal to 1 or more.' # noqa: EM101 + ) + except (ValueError, TypeError): + raise ValidationError( + '"Minimum # of" operator must have a valid numeric Value equal to 1 or more.' # noqa: EM101 + ) from None def _validate_operator_title(operator): """Ensure Title is defined for every operator. @@ -363,7 +376,7 @@ def _validate_elective_value_and_child_courses( if child["data"]["node_type"] == "operator": _validate_operator_title(child) if ( - operator["data"]["operator"] + child["data"]["operator"] == ProgramRequirement.Operator.MIN_NUMBER_OF.value ): _validate_elective_value_presence(child) diff --git a/courses/models.py b/courses/models.py index ea099e99b5..362518d119 100644 --- a/courses/models.py +++ b/courses/models.py @@ -2279,6 +2279,26 @@ def is_operator(self): """True if the node is an operator""" return self.node_type == ProgramRequirementNodeType.OPERATOR + def clean(self): + """Validate the program requirement fields""" + super().clean() + + # Validate operator_value for MIN_NUMBER_OF operators + if ( + self.operator == self.Operator.MIN_NUMBER_OF + and self.operator_value is not None + ): + try: + value = int(self.operator_value) + if value < 1: + raise ValidationError( + {"operator_value": "Minimum # of value must be 1 or greater."} + ) + except (ValueError, TypeError): + raise ValidationError( + {"operator_value": "Minimum # of value must be a valid number."} + ) from None + @property def is_course(self): """True if the node references a course"""