From 4821cfb748f7da22ddb84c0174284689f70d22ff Mon Sep 17 00:00:00 2001 From: Collin Preston Date: Wed, 12 Jul 2023 12:15:33 -0400 Subject: [PATCH 1/5] Don't save the program if there are errors --- courses/forms.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/courses/forms.py b/courses/forms.py index 79f3dae0c..3110c4110 100644 --- a/courses/forms.py +++ b/courses/forms.py @@ -231,10 +231,11 @@ def _serialize(node): return [_serialize(node) for node in data] - def save(self, commit=True): + def save(self, commit=False): """Save requirements""" - program = super().save(commit=commit) + program = super().save(commit=False) transaction.on_commit(self.save_requirements) + program.save_m2m() return program def save_requirements(self): From f4c8fb511a6175b3fa8d36615c8e7aa4ba8cc35e Mon Sep 17 00:00:00 2001 From: Collin Preston Date: Fri, 14 Jul 2023 13:45:10 -0400 Subject: [PATCH 2/5] Program django admin validation error messages Errors messages are displayed for: - Minimum # of" operator must have Value equal to 1 or more. - Minimum # of" operator must have Value equal to 1 or more. - Minimum # of" operator must have Value equal to or less than the number of elective courses in the section. - Section must have a Title. This also adds operator_value and elective_flag to the default requirements section in order to allow the user to change the section to an elective section. --- courses/forms.py | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/courses/forms.py b/courses/forms.py index 3110c4110..cf5693a45 100644 --- a/courses/forms.py +++ b/courses/forms.py @@ -1,5 +1,5 @@ from django.db import transaction -from django.forms import ModelForm +from django.forms import ModelForm, ValidationError from django.forms.fields import JSONField from webpack_loader import utils as webpack_loader_utils @@ -200,7 +200,9 @@ def __init__(self, *args, **kwargs): "data": { "node_type": ProgramRequirementNodeType.OPERATOR.value, "title": "Required Courses", + "operator_value": None, "operator": ProgramRequirement.Operator.ALL_OF.value, + "elective_flag": False, }, "children": [], }, @@ -231,14 +233,38 @@ def _serialize(node): return [_serialize(node) for node in data] + def clean(self): + if "requirements" in self.cleaned_data: + for operator in self.cleaned_data["requirements"]: + if ( + operator["data"]["operator"] + == ProgramRequirement.Operator.MIN_NUMBER_OF.value + ): + if "operator_value" not in operator["data"]: + raise ValidationError( + '"Minimum # of" operator must have Value equal to 1 or more.' + ) + if operator["data"]["operator_value"] == "": + raise ValidationError( + '"Minimum # of" operator must have Value equal to 1 or more.' + ) + if len(operator["children"]) < int( + operator["data"]["operator_value"] + ): + raise ValidationError( + '"Minimum # of" operator must have Value equal to or less than the number of elective courses in the section.' + ) + if operator["data"]["title"] == "": + raise ValidationError("Section must have a Title.") + def save(self, commit=False): """Save requirements""" program = super().save(commit=False) - transaction.on_commit(self.save_requirements) - program.save_m2m() + transaction.on_commit(self._save_requirements) + self.save_m2m() return program - def save_requirements(self): + def _save_requirements(self): """ Save related program requirements. """ From 2d54f5595b5bafb4be8a0ee2cc3f1d08b9404f62 Mon Sep 17 00:00:00 2001 From: Collin Preston Date: Fri, 14 Jul 2023 13:51:15 -0400 Subject: [PATCH 3/5] Revert save method changes --- courses/forms.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/courses/forms.py b/courses/forms.py index cf5693a45..b844c46c8 100644 --- a/courses/forms.py +++ b/courses/forms.py @@ -257,11 +257,10 @@ def clean(self): if operator["data"]["title"] == "": raise ValidationError("Section must have a Title.") - def save(self, commit=False): + def save(self, commit=True): """Save requirements""" - program = super().save(commit=False) + program = super().save(commit=commit) transaction.on_commit(self._save_requirements) - self.save_m2m() return program def _save_requirements(self): From b61410b84f210cf16b066bc65ea515a16c9cf5f8 Mon Sep 17 00:00:00 2001 From: Collin Preston Date: Fri, 14 Jul 2023 15:33:21 -0400 Subject: [PATCH 4/5] Handle elective stipulations --- courses/forms.py | 71 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/courses/forms.py b/courses/forms.py index b844c46c8..535081099 100644 --- a/courses/forms.py +++ b/courses/forms.py @@ -234,28 +234,67 @@ def _serialize(node): return [_serialize(node) for node in data] def clean(self): + def _verify_elective_operator(operator): + """ + Verifies that a Program's elective operator contains + a defined Value field. + + Args: + operator (dict): + { + 'children': [], + 'id': None, + 'data': { + 'node_type': 'operator', + 'title': 'Only 1 of', + 'operator': 'min_number_of', + 'operator_value': '1', + 'elective_flag': False + } + } + + Raises: + ValidationError: operator_value does not exist. + ValidationError: operator_value does exist but is empty. + ValidationError: operator_value is not equal to or less than the total number of child courses. + """ + if ( + operator["data"]["operator"] + == ProgramRequirement.Operator.MIN_NUMBER_OF.value + ): + if "operator_value" not in operator["data"]: + raise ValidationError( + '"Minimum # of" operator must have Value equal to 1 or more.' + ) + if operator["data"]["operator_value"] == "": + raise ValidationError( + '"Minimum # of" operator must have Value equal to 1 or more.' + ) + + # Check all of the child nodes to count the total number of elective courses + # and find if an elective stipulation exists. + total_child_courses = 0 + for child in operator["children"]: + if child["data"]["node_type"] == "operator": + _verify_elective_operator(child) + else: + # Assume the child must be a course. + total_child_courses += 1 + + if total_child_courses < int(operator["data"]["operator_value"]): + raise ValidationError( + '"Minimum # of" operator must have Value equal to or less than the number of elective courses in the section.' + ) + if operator["data"]["title"] == "": + raise ValidationError("Operator must have a Title.") + if "requirements" in self.cleaned_data: for operator in self.cleaned_data["requirements"]: if ( operator["data"]["operator"] == ProgramRequirement.Operator.MIN_NUMBER_OF.value ): - if "operator_value" not in operator["data"]: - raise ValidationError( - '"Minimum # of" operator must have Value equal to 1 or more.' - ) - if operator["data"]["operator_value"] == "": - raise ValidationError( - '"Minimum # of" operator must have Value equal to 1 or more.' - ) - if len(operator["children"]) < int( - operator["data"]["operator_value"] - ): - raise ValidationError( - '"Minimum # of" operator must have Value equal to or less than the number of elective courses in the section.' - ) - if operator["data"]["title"] == "": - raise ValidationError("Section must have a Title.") + _verify_elective_operator(operator) def save(self, commit=True): """Save requirements""" From 7e90b1c87113c13f7d69dd46a348f7dfca317278 Mon Sep 17 00:00:00 2001 From: Collin Preston Date: Fri, 14 Jul 2023 15:35:59 -0400 Subject: [PATCH 5/5] Improve commenting --- courses/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courses/forms.py b/courses/forms.py index 535081099..fbdf9d9ad 100644 --- a/courses/forms.py +++ b/courses/forms.py @@ -272,7 +272,7 @@ def _verify_elective_operator(operator): ) # Check all of the child nodes to count the total number of elective courses - # and find if an elective stipulation exists. + # and validate any elective stipulations. total_child_courses = 0 for child in operator["children"]: if child["data"]["node_type"] == "operator":