Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a total credits field Degrees on the backend #37

Merged
merged 1 commit into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions backend/degree/management/commands/load_degrees.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"]:
Expand Down
19 changes: 19 additions & 0 deletions backend/degree/migrations/0005_degree_credits.py
Original file line number Diff line number Diff line change
@@ -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,
),
]
10 changes: 10 additions & 0 deletions backend/degree/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
2 changes: 1 addition & 1 deletion backend/degree/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
31 changes: 25 additions & 6 deletions backend/degree/utils/parse_degreeworks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand All @@ -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 (
Expand All @@ -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
Loading