diff --git a/backend/degree/models.py b/backend/degree/models.py index 9a2f5995..f7b4ddd5 100644 --- a/backend/degree/models.py +++ b/backend/degree/models.py @@ -191,9 +191,11 @@ class Rule(models.Model): ) def __str__(self) -> str: + rules_str = ", ".join([str(rule) for rule in self.children.all()]) return ( f"{self.title}, q={self.q}, num={self.num}, cus={self.credits}, " - "parent={self.parent.title if self.parent else None}" + f"child rules: {rules_str}" + f"parent={self.parent.title if self.parent else None}" ) @property diff --git a/backend/degree/serializers.py b/backend/degree/serializers.py index 5cacb886..66ca3efa 100644 --- a/backend/degree/serializers.py +++ b/backend/degree/serializers.py @@ -1,5 +1,6 @@ from textwrap import dedent +from collections import deque from django.db.models import Q from rest_framework import serializers @@ -139,6 +140,30 @@ def validate(self, data): if degree_plan is None: degree_plan = self.instance.degree_plan + # Find rules in degree plan with requirements identical to currently fulfilled ones + bfs_queue = deque() + for degree in degree_plan.degrees.all(): + for rule_in_degree in degree.rules.all(): + bfs_queue.append(rule_in_degree) + + identical_rules = [] + existing_fulfillment = Fulfillment.objects.filter( + degree_plan=degree_plan, + full_code=full_code + ).first() + existing_rules = existing_fulfillment.rules.all() if existing_fulfillment is not None else [] + while len(bfs_queue): + curr_rule = bfs_queue.pop() + # this is a leaf rule + if curr_rule.q: + if any(rule.q == curr_rule.q and rule.id != curr_rule.id for rule in rules) and curr_rule not in existing_rules: + identical_rules.append(curr_rule) + else: # parent rule + bfs_queue.extend(curr_rule.children.all()) + + rules.extend(identical_rules) + data["rules"] = rules + # TODO: check that rules belong to this degree plan for rule in rules: # NOTE: we don't do any validation if the course doesn't exist in DB. In future,