From e1c74e2d0a151375459c098086a257135226f5fa Mon Sep 17 00:00:00 2001 From: aadalal <57609353+AaDalal@users.noreply.github.com> Date: Mon, 5 Feb 2024 01:12:23 -0500 Subject: [PATCH] Get all written tests to pass --- backend/degree/models.py | 31 +++++++----- backend/degree/serializers.py | 18 +------ backend/tests/degree/test_api.py | 81 ++++++++++++-------------------- 3 files changed, 50 insertions(+), 80 deletions(-) diff --git a/backend/degree/models.py b/backend/degree/models.py index 121a443b1..50c44bb91 100644 --- a/backend/degree/models.py +++ b/backend/degree/models.py @@ -12,6 +12,7 @@ from courses.models import Course from degree.utils.model_utils import q_object_parser +from django.db.models.signals import m2m_changed program_choices = [ @@ -334,7 +335,7 @@ class Fulfillment(models.Model): rules = models.ManyToManyField( Rule, - related_name="fulfillments", + related_name="+", blank=True, help_text=dedent( """ @@ -365,17 +366,26 @@ def save(self, *args, **kwargs): self.historical_course = course - fulfillment = super().save(*args, **kwargs) - - # Update rule statuses - for rule in self.rules.all(): + super().save(*args, **kwargs) + +def update_satisfaction_statuses(sender, instance, action, pk_set, **kwargs): + """ + This function updates the SatisfactionStatuses associated with a DegreePlan when the rules + associated with a Fulfillment change. + """ + if action == "pre_clear" or action == "pre_remove": + instance.degree_plan.satisfactions.filter(rule__in=pk_set).delete() + return + + if action == "post_add" or action == "post_remove" or action == "post_clear": + degree_plan = instance.degree_plan + for rule in degree_plan.degree.rules.all(): status, _ = SatisfactionStatus.objects.get_or_create( - degree_plan=self.degree_plan, rule=rule + degree_plan=degree_plan, rule=rule ) - status.satisfied = rule.evaluate([self.full_code]) + status.satisfied = rule.evaluate([fulfillment.full_code for fulfillment in degree_plan.fulfillments.all()]) status.save() - - return fulfillment +m2m_changed.connect(update_satisfaction_statuses, sender=Fulfillment.rules.through) class SatisfactionStatus(models.Model): @@ -386,7 +396,6 @@ class SatisfactionStatus(models.Model): related_name="satisfactions", help_text="The degree plan that leads to the satisfaction of the rule", ) - rule = models.ForeignKey( Rule, on_delete=models.CASCADE, @@ -394,9 +403,7 @@ class SatisfactionStatus(models.Model): related_name="+", help_text="The rule that is satisfied", ) - satisfied = models.BooleanField(default=False, help_text="Whether the rule is satisfied") - last_updated = models.DateTimeField(auto_now=True) last_checked = models.DateTimeField(default=timezone.now) diff --git a/backend/degree/serializers.py b/backend/degree/serializers.py index 0cdef1106..09b8bcbd9 100644 --- a/backend/degree/serializers.py +++ b/backend/degree/serializers.py @@ -39,21 +39,6 @@ class Meta: fields = "__all__" read_only_fields = "__all__" - -class PathParamPrimaryKeyDefault: - requires_context = True - - def __call__(self, serializer_field): - print("CALLED") - return DegreePlan.objects.get( - person=serializer_field.context['request'].user, - pk=serializer_field.context['view'].get_degree_plan_id() - ) - - def __repr__(self): - return '%s()' % self.__class__.__name__ - - class FulfillmentSerializer(serializers.ModelSerializer): course = CourseListSerializer( read_only=True, @@ -75,14 +60,13 @@ def to_internal_value(self, data): class Meta: model = Fulfillment - fields = ["degree_plan", "full_code", "course", "semester", "rules"] + fields = ["id", "degree_plan", "full_code", "course", "semester", "rules"] def validate(self, data): data = super().validate(data) rules = data.get("rules") # for patch requests without a rules field full_code = data.get("full_code") degree_plan = data.get("degree_plan") - print(data) if rules is None and full_code is None: return data # Nothing to validate diff --git a/backend/tests/degree/test_api.py b/backend/tests/degree/test_api.py index 73e25fef2..bc55fca2c 100644 --- a/backend/tests/degree/test_api.py +++ b/backend/tests/degree/test_api.py @@ -43,6 +43,15 @@ def test_delete_user_degree_plan_with_fulfillments(self): pass class FulfillmentViewsetTest(TestCase): + def assertSerializedFulfillmentEquals(self, fulfillment: dict, expected: Fulfillment): + self.assertEqual(len(fulfillment), 6) + self.assertEqual(fulfillment["id"], expected.id) + self.assertEqual(fulfillment["course"], CourseListSerializer(expected.historical_course).data) + self.assertEqual(fulfillment["rules"], [rule.id for rule in expected.rules.all()]) + self.assertEqual(fulfillment["semester"], expected.semester) + self.assertEqual(fulfillment["degree_plan"], expected.degree_plan.id) + self.assertEqual(fulfillment["full_code"], expected.full_code) + def setUp(self): self.user = User.objects.create_user( username="test", password="top_secret", email="test@example.com" @@ -109,6 +118,8 @@ def test_create_fulfillment(self): {"full_code": "CIS-1200", "semester": TEST_SEMESTER, "rules": [self.rule1.id]} ) self.assertEqual(response.status_code, 201, response.json()) + self.assertSerializedFulfillmentEquals(response.data, Fulfillment.objects.get(full_code="CIS-1200")) + fulfillment = Fulfillment.objects.get(full_code="CIS-1200") self.assertEqual(fulfillment.degree_plan, self.degree_plan) self.assertEqual(fulfillment.historical_course, self.cis_1200) @@ -137,24 +148,10 @@ def test_list_fulfillment(self): response = self.client.get(reverse("degreeplan-fulfillment-list", kwargs={"degreeplan_pk": self.degree_plan.id})) self.assertEqual(response.status_code, 200, response.json()) - fulfillments = sorted((dict(d) for d in response.data), key=lambda d: d["full_code"]) - fulfillments == [ - { - "degree_plan": self.degree_plan.id, - "full_code": "CIS-1200", - "course": CourseListSerializer(self.cis_1200).data, - "semester": TEST_SEMESTER, - "rules": [self.rule2.id] - }, - { - - "degree_plan": self.degree_plan.id, - "full_code": "CIS-1910", - "course": dict(CourseListSerializer(self.cis_1910).data), - "semester": None, - "rules": [self.rule1.id] - } - ] + response_a, response_b = sorted((dict(d) for d in response.data), key=lambda d: d["full_code"]) + + self.assertSerializedFulfillmentEquals(response_a, a) + self.assertSerializedFulfillmentEquals(response_b, b) def test_retrieve_fulfillment(self): a = Fulfillment( @@ -167,15 +164,7 @@ def test_retrieve_fulfillment(self): response = self.client.get(reverse("degreeplan-fulfillment-detail", kwargs={"degreeplan_pk": self.degree_plan.id, "pk": a.id})) self.assertEqual(response.status_code, 200, response.json()) - fulfillment = response.data - print(fulfillment) - assert fulfillment == { - "full_code": "CIS-1200", - "degree_plan": self.degree_plan.id, - "course": CourseListSerializer(self.cis_1200).data, - "semester": TEST_SEMESTER, - "rules": [self.rule1] - } + self.assertSerializedFulfillmentEquals(response.data, a) def test_update_fulfillment_replace_rule(self): @@ -192,11 +181,10 @@ def test_update_fulfillment_replace_rule(self): {"rules": [self.rule3.id]} ) self.assertEqual(response.status_code, 200, response.json()) - fulfillment = response.data - self.assertEquals(fulfillment["course"], CourseListSerializer(self.cis_1200).data) - self.assertEquals(fulfillment["rules"], [self.rule3.id]) - self.assertEquals(fulfillment["semester"], TEST_SEMESTER) - self.assertEquals(fulfillment["degree_plan"], self.degree_plan.id) + self.assertSerializedFulfillmentEquals(response.data, a) + a.refresh_from_db() + self.assertEqual(a.rules.count(), 1) + self.assertEqual(a.rules.first(), self.rule3) def test_update_semester(self): a = Fulfillment( @@ -212,13 +200,9 @@ def test_update_semester(self): {"semester": "2022B"} ) self.assertEqual(response.status_code, 200, response.json()) - fulfillment = response.data - self.assertEquals(fulfillment["course"], CourseListSerializer(self.cis_1200).data) - self.assertEquals(fulfillment["rules"], [self.rule1.id]) - self.assertEquals(fulfillment["semester"], "2022B") - self.assertEquals(fulfillment["degree_plan"], self.degree_plan.id) a.refresh_from_db() - self.assertEquals(a.semester, "2022B") + self.assertSerializedFulfillmentEquals(response.data, a) + self.assertEqual(a.semester, "2022B") def test_update_fulfillment_full_code(self): a = Fulfillment( @@ -234,13 +218,9 @@ def test_update_fulfillment_full_code(self): {"full_code": "CIS-1910"} ) self.assertEqual(response.status_code, 200, response.json()) - fulfillment = response.data - self.assertEquals(fulfillment["course"], CourseListSerializer(self.cis_1910).data) - self.assertEquals(fulfillment["rules"], [self.rule3.id]) - self.assertEquals(fulfillment["semester"], TEST_SEMESTER) - self.assertEquals(fulfillment["degree_plan"], self.degree_plan.id) a.refresh_from_db() - self.assertEquals(a.full_code, "CIS-1910") + self.assertSerializedFulfillmentEquals(response.data, a) + self.assertEqual(a.full_code, "CIS-1910") def test_update_fulfillment_rule(self): a = Fulfillment( @@ -256,11 +236,10 @@ def test_update_fulfillment_rule(self): {"rules": [self.rule3.id, self.rule1.id]} ) self.assertEqual(response.status_code, 200, response.json()) - fulfillment = response.data - self.assertEquals(fulfillment["course"], CourseListSerializer(self.cis_1200).data) - self.assertEquals(fulfillment["rules"], [self.rule3.id]) - self.assertEquals(fulfillment["semester"], TEST_SEMESTER) - self.assertEquals(fulfillment["degree_plan"], self.degree_plan.id) + a.refresh_from_db() + self.assertSerializedFulfillmentEquals(response.data, a) + self.assertEqual(a.rules.count(), 2) + self.assertEqual(set(a.rules.all()), {self.rule1, self.rule3}) def test_update_fulfillment_add_violated_rule(self): a = Fulfillment( @@ -276,7 +255,7 @@ def test_update_fulfillment_add_violated_rule(self): {"rules": [self.rule2.id, self.rule1.id]} ) self.assertEqual(response.status_code, 400, response.json()) - self.assertEquals( + self.assertEqual( response.data["non_field_errors"][0], f"Course CIS-1200 does not satisfy rule {self.rule2.id}" ) @@ -295,7 +274,7 @@ def test_update_fulfillment_full_code_violates_rule(self): {"full_code": "CIS-1910"} ) self.assertEqual(response.status_code, 400, response.json()) - self.assertEquals( + self.assertEqual( response.data["non_field_errors"][0], f"Course CIS-1910 does not satisfy rule {self.rule1.id}" )