From 61e2670f1e5af37bf85e09cfa96fc0a4cebe607b Mon Sep 17 00:00:00 2001 From: aadalal <57609353+AaDalal@users.noreply.github.com> Date: Tue, 20 Feb 2024 20:24:58 -0500 Subject: [PATCH] add total credits --- .../management/commands/load_degrees.py | 7 +++-- .../degree/migrations/0005_degree_credits.py | 19 ++++++++++++ backend/degree/models.py | 10 ++++++ backend/degree/serializers.py | 2 +- backend/degree/utils/parse_degreeworks.py | 31 +++++++++++++++---- 5 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 backend/degree/migrations/0005_degree_credits.py diff --git a/backend/degree/management/commands/load_degrees.py b/backend/degree/management/commands/load_degrees.py index 46442e17e..e81db63f2 100644 --- a/backend/degree/management/commands/load_degrees.py +++ b/backend/degree/management/commands/load_degrees.py @@ -43,6 +43,7 @@ def handle(self, *args, **kwargs): directory = kwargs["directory"] assert path.isdir(directory), f"{directory} is not a directory" + created_count = 0 for degree_file in listdir(directory): year, program, degree, major, concentration = re.match( r"(\d+)-(\w+)-(\w+)-(\w+)(?:-(\w+))?", degree_file @@ -74,14 +75,16 @@ def handle(self, *args, **kwargs): concentration=concentration, year=year, ) - degree.save() with open(path.join(directory, degree_file)) as f: degree_json = json.load(f) if kwargs["verbosity"]: print(f"Parsing and saving degree {degree}...") - parse_and_save_degreeworks(degree_json, degree) + created_count += parse_and_save_degreeworks(degree_json, degree) + + if kwargs["verbosity"]: + print(f"Created {created_count} degrees") if kwargs["deduplicate_rules"]: if kwargs["verbosity"]: diff --git a/backend/degree/migrations/0005_degree_credits.py b/backend/degree/migrations/0005_degree_credits.py new file mode 100644 index 000000000..cd4bf7ae5 --- /dev/null +++ b/backend/degree/migrations/0005_degree_credits.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.23 on 2024-02-20 21:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('degree', '0004_remove_fulfillment_historical_course'), + ] + + operations = [ + migrations.AddField( + model_name='degree', + name='credits', + field=models.DecimalField(decimal_places=2, default=32, help_text='\nThe minimum number of CUs required for this degree.\n', max_digits=4), + preserve_default=False, + ), + ] diff --git a/backend/degree/models.py b/backend/degree/models.py index afe29b518..4fe5682b5 100644 --- a/backend/degree/models.py +++ b/backend/degree/models.py @@ -81,6 +81,16 @@ class Degree(models.Model): """ ), ) + + credits = models.DecimalField( + decimal_places=2, + max_digits=4, + help_text=dedent( + """ + The minimum number of CUs required for this degree. + """ + ), + ) class Meta: constraints = [ diff --git a/backend/degree/serializers.py b/backend/degree/serializers.py index c004af7bd..47ac37066 100644 --- a/backend/degree/serializers.py +++ b/backend/degree/serializers.py @@ -100,7 +100,7 @@ def validate(self, data): class DegreePlanListSerializer(serializers.ModelSerializer): - # degrees = DegreeListSerializer(read_only=True, many=True) + degrees = DegreeListSerializer(read_only=True, many=True) id = serializers.ReadOnlyField(help_text="The id of the DegreePlan.") class Meta: diff --git a/backend/degree/utils/parse_degreeworks.py b/backend/degree/utils/parse_degreeworks.py index 98ce16481..116b13dae 100644 --- a/backend/degree/utils/parse_degreeworks.py +++ b/backend/degree/utils/parse_degreeworks.py @@ -272,16 +272,22 @@ def parse_rulearray( raise LookupError(f"Unknown rule type {rule_json['ruleType']}") -# TODO: Make the function names more descriptive -def parse_degreeworks(json: dict, degree: Degree) -> list[Rule]: +def parse_degreeworks(json: dict, degree: Degree) -> list[Rule] | None: """ Returns a list of Rules given a DegreeWorks JSON audit and a Degree. - Note that this method creates rule objects but does not save them. + Note that this method creates rule objects but does not save them. If it returns null, + it indicated that this json could not be parsed, meaning that the degree is invalid and + should not be saved. """ blockArray = json["blockArray"] rules = [] - for requirement in blockArray: + # get total credits requirement for the degree + if requirement["requirementType"] == "DEGREE": + for qualifier in requirement["header"]["qualifierArray"]: + if qualifier.get("label") == "Minimum Total Credits Required": + degree.credits = float(qualifier["credits"]) + degree_req = Rule( title=requirement["title"], # TODO: use requirement code? @@ -294,15 +300,26 @@ def parse_degreeworks(json: dict, degree: Degree) -> list[Rule]: # check if this requirement actually has anything in it if degree_req == rules[-1] and not degree_req.q: rules.pop() + + # special case for Additional majors + if degree.credits is None: + logging.error("Skipped degree because it has not total credits requirement.") + return None + return rules -def parse_and_save_degreeworks(json: dict, degree: Degree) -> None: +def parse_and_save_degreeworks(json: dict, degree: Degree) -> bool: """ Parses a DegreeWorks JSON audit and saves the rules to the database. + + Returns true if the degree was saved, and false if it was not. """ - degree.save() rules = parse_degreeworks(json, degree) + if rules is None: + return False + + degree.save() for rule in rules: if rule.q: assert ( @@ -313,3 +330,5 @@ def parse_and_save_degreeworks(json: dict, degree: Degree) -> None: for rule in top_level_rules: rule.refresh_from_db() degree.rules.add(rule) + + return True