From b7fb5160df7346b77db58ea1a8b9585484ce9c41 Mon Sep 17 00:00:00 2001 From: Sushil Tiwari Date: Tue, 14 Oct 2025 14:43:32 +0545 Subject: [PATCH] fix(dref): Handle auto creation of instance for M2M fields --- dref/serializers.py | 29 +++-- dref/test_views.py | 262 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 270 insertions(+), 21 deletions(-) diff --git a/dref/serializers.py b/dref/serializers.py index 27d0b4ce0..6cbc747c9 100644 --- a/dref/serializers.py +++ b/dref/serializers.py @@ -42,18 +42,24 @@ class RiskSecuritySerializer(ModelSerializer): + id = serializers.IntegerField(required=False) + class Meta: model = RiskSecurity fields = "__all__" class SourceInformationSerializer(ModelSerializer): + id = serializers.IntegerField(required=False) + class Meta: model = SourceInformation fields = "__all__" class PlannedInterventionIndicatorsSerializer(ModelSerializer): + id = serializers.IntegerField(required=False) + class Meta: model = PlannedInterventionIndicators fields = "__all__" @@ -274,8 +280,13 @@ def get_unpublished_final_report_count(self, obj) -> int: return DrefFinalReport.objects.filter(dref_id=obj.id, is_published=False).count() -class PlannedInterventionSerializer(ModelSerializer): +class PlannedInterventionSerializer( + NestedCreateMixin, + NestedUpdateMixin, + ModelSerializer, +): # budget_file_details = DrefFileSerializer(source="budget_file", read_only=True) + id = serializers.IntegerField(required=False) image_url = serializers.SerializerMethodField() indicators = PlannedInterventionIndicatorsSerializer(many=True, required=False) title_display = serializers.CharField(source="get_title_display", read_only=True) @@ -284,20 +295,6 @@ class Meta: model = PlannedIntervention fields = "__all__" - def create(self, validated_data): - indicators = validated_data.pop("indicators", []) - intervention = super().create(validated_data) - for indicator in indicators: - ind_object = PlannedInterventionIndicators.objects.create(**indicator) - intervention.indicators.add(ind_object) - return intervention - - def update(self, instance, validated_data): - # TODO: implement this - # indicators = validated_data.pop("indicators", []) - intervention = super().update(instance, validated_data) - return intervention - def get_image_url(self, plannedintervention) -> str: title = plannedintervention.title if title and self.context and "request" in self.context: @@ -307,6 +304,7 @@ def get_image_url(self, plannedintervention) -> str: class NationalSocietyActionSerializer(ModelSerializer): + id = serializers.IntegerField(required=False) image_url = serializers.SerializerMethodField() title_display = serializers.CharField(source="get_title_display", read_only=True) @@ -329,6 +327,7 @@ def get_image_url(self, nationalsocietyactions) -> str: class IdentifiedNeedSerializer(ModelSerializer): + id = serializers.IntegerField(required=False) image_url = serializers.SerializerMethodField() title_display = serializers.CharField(source="get_title_display", read_only=True) diff --git a/dref/test_views.py b/dref/test_views.py index 24229e12c..fd91e6365 100644 --- a/dref/test_views.py +++ b/dref/test_views.py @@ -180,7 +180,23 @@ def test_post_dref_creation(self, send_notification): "originator_email": "test@gmail.com", "national_society": national_society.id, "disaster_type": disaster_type.id, - "needs_identified": [{"title": "shelter_housing_and_settlements", "description": "hey"}], + # NOTE: Test Many to Many fields + "risk_security": [ + {"risk": "Test Risk 1", "mitigation_measure": "Test Mitigation Measure"}, + {"risk": "Test Risk 2", "mitigation_measure": "Test Mitigation Measure 1"}, + ], + "source_information": [ + {"source_name": "Test Source 1", "link": "http://test.com"}, + {"source_name": "Test Source 2", "link": "http://test.com"}, + ], + "needs_identified": [ + {"title": "shelter_housing_and_settlements", "description": "Shelter"}, + {"title": "health", "description": "Health"}, + ], + "national_society_actions": [ + {"title": "coordination", "description": "Coordination"}, + {"title": "shelter_housing_and_settlements", "description": "Shelter"}, + ], "planned_interventions": [ { "title": "shelter_housing_and_settlements", @@ -190,14 +206,18 @@ def test_post_dref_creation(self, send_notification): "female": 2255, "indicators": [ { - "title": "test_title", + "title": "test_title_1", "actual": 21232, "target": 44444, - } + }, + { + "title": "test_title_2", + "actual": 21232, + "target": 44444, + }, ], }, { - "id": 2, "title": "health", "description": "matrix reloaded", "budget": 451111111, @@ -205,10 +225,15 @@ def test_post_dref_creation(self, send_notification): "female": 2255, "indicators": [ { - "title": "test_title", + "title": "test_title_2", "actual": 21232, "target": 44444, - } + }, + { + "title": "test_title_2", + "actual": 21232, + "target": 44444, + }, ], }, ], @@ -228,6 +253,231 @@ def test_post_dref_creation(self, send_notification): self.assertTrue(send_notification.assert_called) self.assertEqual(email_data["title"], instance.title) + # Testing Nested objects creation and update + # Update the created dref + data = { + "modified_at": datetime.now(), + **data, + "risk_security": [ + {"id": response.data["risk_security"][0]["id"], "risk": "Updated Test Risk 1", "mitigation_measure": "Test"}, + { + "id": response.data["risk_security"][1]["id"], + "risk": "Updated Test Risk 2", + "mitigation_measure": "Test Mitigation Measure 2", + }, + # New item to be added + {"risk": "New Test Risk", "mitigation_measure": "New Test Mitigation Measure"}, + ], + "source_information": [ + { + "id": response.data["source_information"][0]["id"], + "source_name": "Updated Test Source 1", + "link": "http://updated.com", + }, + { + "id": response.data["source_information"][1]["id"], + "source_name": "Updated Test Source 2", + "link": "http://updated.com", + }, + # New item to be added + {"source_name": "New Test Source", "link": "http://new.com"}, + ], + "needs_identified": [ + { + "id": response.data["needs_identified"][0]["id"], + "title": "shelter_housing_and_settlements", + "description": "Updated Shelter", + }, + {"id": response.data["needs_identified"][1]["id"], "title": "health", "description": "Updated Health"}, + # New item to be added + {"title": "water_sanitation_and_hygiene", "description": "WASH"}, + ], + "national_society_actions": [ + { + "id": response.data["national_society_actions"][0]["id"], + "title": "coordination", + "description": "Updated Coordination", + }, + { + "id": response.data["national_society_actions"][1]["id"], + "title": "shelter_housing_and_settlements", + "description": "Updated Shelter", + }, + # New item to be added + {"title": "health", "description": "Health"}, + ], + "planned_interventions": [ + { + "id": response.data["planned_interventions"][0]["id"], + "title": "shelter_housing_and_settlements", + "description": "Updated matrix", + "budget": 99999, + "male": 12222, + "female": 2255, + "indicators": [ + { + "id": response.data["planned_interventions"][0]["indicators"][0]["id"], + "title": "updated_test_title_1", + "actual": 11111, + "target": 22222, + }, + { + "id": response.data["planned_interventions"][0]["indicators"][1]["id"], + "title": "updated_test_title_2", + "actual": 33333, + "target": 44444, + }, + # New item to be added + { + "title": "new_test_title_3", + "actual": 55555, + "target": 66666, + }, + ], + }, + { + "id": response.data["planned_interventions"][1]["id"], + "title": "health", + "description": "Updated matrix reloaded", + "budget": 88888, + "male": 12222, + "female": 2255, + "indicators": [ + { + "id": response.data["planned_interventions"][1]["indicators"][0]["id"], + "title": "updated_test_title_1", + "actual": 11111, + "target": 22222, + }, + { + "id": response.data["planned_interventions"][1]["indicators"][1]["id"], + "title": "updated_test_title_2", + "actual": 33333, + "target": 44444, + }, + # New item to be added + { + "title": "new_test_title_3", + "actual": 55555, + "target": 66666, + }, + ], + }, + ], + } + + # Update the created dref + url = f"/api/v2/dref/{instance.id}/" + response = self.client.patch(url, data, format="json") + self.assert_200(response) + + # Risk Security should have 3 items now + self.assertEqual(len(response.data["risk_security"]), 3) + self.assertEqual( + { + response.data["risk_security"][0]["id"], + response.data["risk_security"][1]["id"], + response.data["risk_security"][2]["risk"], + }, + { + data["risk_security"][0].get("id"), + data["risk_security"][1].get("id"), + data["risk_security"][2].get("risk"), + }, + ) + + # Source of Information should have 3 items now + self.assertEqual(len(response.data["source_information"]), 3) + self.assertEqual( + { + response.data["source_information"][0]["id"], + response.data["source_information"][0]["source_name"], + response.data["source_information"][1]["id"], + response.data["source_information"][1]["source_name"], + response.data["source_information"][2]["source_name"], + }, + { + data["source_information"][0].get("id"), + data["source_information"][0].get("source_name"), + data["source_information"][1].get("id"), + data["source_information"][1].get("source_name"), + data["source_information"][2].get("source_name"), + }, + ) + + # Needs Identified should have 3 items mock_now + self.assertEqual(len(response.data["needs_identified"]), 3) + self.assertEqual( + { + response.data["needs_identified"][0]["id"], + response.data["needs_identified"][0]["title"], + response.data["needs_identified"][1]["id"], + response.data["needs_identified"][1]["title"], + response.data["needs_identified"][2]["title"], + }, + { + data["needs_identified"][0].get("id"), + data["needs_identified"][0].get("title"), + data["needs_identified"][1].get("id"), + data["needs_identified"][1].get("title"), + data["needs_identified"][2].get("title"), + }, + ) + + # National Society Actions should have 3 items mock_now + self.assertEqual(len(response.data["national_society_actions"]), 3) + self.assertEqual( + { + response.data["national_society_actions"][0]["id"], + response.data["national_society_actions"][0]["title"], + response.data["national_society_actions"][1]["id"], + response.data["national_society_actions"][1]["title"], + response.data["national_society_actions"][2]["title"], + }, + { + data["national_society_actions"][0].get("id"), + data["national_society_actions"][0].get("title"), + data["national_society_actions"][1].get("id"), + data["national_society_actions"][1].get("title"), + data["national_society_actions"][2].get("title"), + }, + ) + + # Planned Interventions should have 2 items + self.assertEqual(len(response.data["planned_interventions"]), 2) + self.assertEqual( + { + response.data["planned_interventions"][0]["id"], + response.data["planned_interventions"][0]["title"], + response.data["planned_interventions"][1]["id"], + response.data["planned_interventions"][1]["title"], + }, + { + data["planned_interventions"][0].get("id"), + data["planned_interventions"][0].get("title"), + data["planned_interventions"][1].get("id"), + data["planned_interventions"][1].get("title"), + }, + ) + + # Planned Intervention - Indicators should have 3 items now + self.assertEqual( + { + response.data["planned_interventions"][0]["indicators"][0]["id"], + response.data["planned_interventions"][0]["indicators"][0]["title"], + response.data["planned_interventions"][0]["indicators"][1]["id"], + response.data["planned_interventions"][0]["indicators"][1]["title"], + response.data["planned_interventions"][0]["indicators"][2]["title"], + }, + { + data["planned_interventions"][0]["indicators"][0].get("id"), + data["planned_interventions"][0]["indicators"][0].get("title"), + data["planned_interventions"][0]["indicators"][1].get("id"), + data["planned_interventions"][0]["indicators"][1].get("title"), + data["planned_interventions"][0]["indicators"][2].get("title"), + }, + ) + def test_event_date_in_dref(self): """ Test for the event date based on type_of_onset