| 
1 | 1 | import datetime  | 
2 | 2 | import os  | 
 | 3 | +from ast import literal_eval as ev  | 
3 | 4 | from typing import List, Optional  | 
4 | 5 | 
 
  | 
5 | 6 | from django.conf import settings  | 
@@ -288,7 +289,12 @@ def create(self, validated_data):  | 
288 | 289 |         indicators = validated_data.pop("indicators", [])  | 
289 | 290 |         intervention = super().create(validated_data)  | 
290 | 291 |         for indicator in indicators:  | 
291 |  | -            ind_object = PlannedInterventionIndicators.objects.create(**indicator)  | 
 | 292 | +            # Instead of ind_object = PlannedInterventionIndicators.objects.create(**indicator):  | 
 | 293 | +            ind_id = indicator.get("id")  | 
 | 294 | +            if ind_id:  | 
 | 295 | +                ind_object = PlannedInterventionIndicators.objects.get(id=ind_id)  | 
 | 296 | +            else:  | 
 | 297 | +                ind_object = PlannedInterventionIndicators.objects.create(**indicator)  | 
292 | 298 |             intervention.indicators.add(ind_object)  | 
293 | 299 |         return intervention  | 
294 | 300 | 
 
  | 
@@ -691,6 +697,83 @@ def update(self, instance, validated_data):  | 
691 | 697 |             raise serializers.ValidationError({"modified_at": settings.DREF_OP_UPDATE_FINAL_REPORT_UPDATE_ERROR_MESSAGE})  | 
692 | 698 |         validated_data["modified_at"] = timezone.now()  | 
693 | 699 |         dref = super().update(instance, validated_data)  | 
 | 700 | + | 
 | 701 | +        # Helpers for robust M2M update  | 
 | 702 | + | 
 | 703 | +        def create_with_m2m(item, model_cls):  | 
 | 704 | +            # Only handle PlannedIntervention/indicators for now  | 
 | 705 | +            m2m_fields = []  | 
 | 706 | +            if model_cls.__name__ == "PlannedIntervention" and "indicators" in item:  | 
 | 707 | +                m2m_fields = ["indicators"]  | 
 | 708 | +            # Remove m2m fields from item for create  | 
 | 709 | +            item_clean = {k: v for k, v in item.items() if k not in m2m_fields}  | 
 | 710 | +            pk = item_clean.pop("pk")  | 
 | 711 | +            obj, created = model_cls.objects.get_or_create(pk=pk, defaults=item_clean)  | 
 | 712 | +            if not created:  | 
 | 713 | +                print("Updated existing PlannedIntervention object:", obj)  | 
 | 714 | + | 
 | 715 | +            # Set m2m fields after create  | 
 | 716 | +            for m2m_field in m2m_fields:  | 
 | 717 | +                values = item[m2m_field]  | 
 | 718 | +                rel_model = obj._meta.get_field(m2m_field).related_model  | 
 | 719 | +                rel_ids = []  | 
 | 720 | +                for v in values:  | 
 | 721 | +                    if isinstance(v, dict):  | 
 | 722 | +                        rel_obj = rel_model.objects.create(**v)  | 
 | 723 | +                        rel_ids.append(rel_obj.id)  | 
 | 724 | +                    else:  | 
 | 725 | +                        rel_ids.append(v)  | 
 | 726 | +                getattr(obj, m2m_field).set(rel_ids)  | 
 | 727 | +            return obj  | 
 | 728 | + | 
 | 729 | +        def m2m(data, model_cls):  | 
 | 730 | +            ids = []  | 
 | 731 | +            for item in data:  | 
 | 732 | +                if isinstance(item, dict):  | 
 | 733 | +                    item_id = item.get("id")  | 
 | 734 | +                    if item_id:  | 
 | 735 | +                        ids.append(item_id)  | 
 | 736 | +                    else:  | 
 | 737 | +                        obj = create_with_m2m(item, model_cls)  | 
 | 738 | +                        ids.append(obj.id)  | 
 | 739 | +                else:  | 
 | 740 | +                    ids.append(item)  | 
 | 741 | +            return ids  | 
 | 742 | + | 
 | 743 | +        # planned_interventions M2M handling  | 
 | 744 | +        planned_interventions_data = self.initial_data.get("planned_interventions")  | 
 | 745 | +        if planned_interventions_data:  | 
 | 746 | +            dref.planned_interventions.set(m2m(planned_interventions_data, PlannedIntervention))  | 
 | 747 | + | 
 | 748 | +        # needs_identified M2M handling  | 
 | 749 | +        needs_identified_data = self.initial_data.get("needs_identified")  | 
 | 750 | +        if needs_identified_data:  | 
 | 751 | +            dref.needs_identified.set(m2m(needs_identified_data, IdentifiedNeed))  | 
 | 752 | + | 
 | 753 | +        # national_society_actions M2M handling  | 
 | 754 | +        national_society_actions_data = self.initial_data.get("national_society_actions")  | 
 | 755 | +        if national_society_actions_data:  | 
 | 756 | +            dref.national_society_actions.set(m2m(national_society_actions_data, NationalSocietyAction))  | 
 | 757 | + | 
 | 758 | +        # risk_security M2M handling  | 
 | 759 | +        risk_security_data = self.initial_data.get("risk_security")  | 
 | 760 | +        if risk_security_data:  | 
 | 761 | +            dref.risk_security.set(m2m(risk_security_data, RiskSecurity))  | 
 | 762 | + | 
 | 763 | +        # source_information M2M handling  | 
 | 764 | +        source_information_data = self.initial_data.get("source_information")  | 
 | 765 | +        if source_information_data:  | 
 | 766 | +            dref.source_information.set(m2m(source_information_data, SourceInformation))  | 
 | 767 | + | 
 | 768 | +        # proposed_action M2M handling  | 
 | 769 | +        proposed_action_data = self.initial_data.get("proposed_action")  | 
 | 770 | +        if proposed_action_data:  | 
 | 771 | +            if isinstance(proposed_action_data, str):  | 
 | 772 | +                proposed_action_data = ev(proposed_action_data)  | 
 | 773 | +            if isinstance(proposed_action_data, dict):  | 
 | 774 | +                proposed_action_data = [proposed_action_data]  | 
 | 775 | +            dref.proposed_action.set(m2m(proposed_action_data, ProposedAction))  | 
 | 776 | + | 
694 | 777 |         if to:  | 
695 | 778 |             transaction.on_commit(lambda: send_dref_email.delay(dref.id, list(to), "Updated"))  | 
696 | 779 |         return dref  | 
@@ -1079,7 +1162,81 @@ def update(self, instance, validated_data):  | 
1079 | 1162 |         if modified_at and instance.modified_at and modified_at < instance.modified_at:  | 
1080 | 1163 |             raise serializers.ValidationError({"modified_at": settings.DREF_OP_UPDATE_FINAL_REPORT_UPDATE_ERROR_MESSAGE})  | 
1081 | 1164 |         validated_data["modified_at"] = timezone.now()  | 
1082 |  | -        return super().update(instance, validated_data)  | 
 | 1165 | +        operational_update = super().update(instance, validated_data)  | 
 | 1166 | + | 
 | 1167 | +        # Helpers for robust M2M update  | 
 | 1168 | + | 
 | 1169 | +        def create_with_m2m(item, model_cls):  | 
 | 1170 | +            # Only handle PlannedIntervention/indicators for now  | 
 | 1171 | +            m2m_fields = []  | 
 | 1172 | +            if model_cls.__name__ == "PlannedIntervention" and "indicators" in item:  | 
 | 1173 | +                m2m_fields = ["indicators"]  | 
 | 1174 | +            # Remove m2m fields from item for create  | 
 | 1175 | +            item_clean = {k: v for k, v in item.items() if k not in m2m_fields}  | 
 | 1176 | +            obj = model_cls.objects.create(**item_clean)  | 
 | 1177 | +            # Set m2m fields after create  | 
 | 1178 | +            for m2m_field in m2m_fields:  | 
 | 1179 | +                values = item[m2m_field]  | 
 | 1180 | +                rel_model = obj._meta.get_field(m2m_field).related_model  | 
 | 1181 | +                rel_ids = []  | 
 | 1182 | +                for v in values:  | 
 | 1183 | +                    if isinstance(v, dict):  | 
 | 1184 | +                        rel_obj = rel_model.objects.create(**v)  | 
 | 1185 | +                        rel_ids.append(rel_obj.id)  | 
 | 1186 | +                    else:  | 
 | 1187 | +                        rel_ids.append(v)  | 
 | 1188 | +                getattr(obj, m2m_field).set(rel_ids)  | 
 | 1189 | +            return obj  | 
 | 1190 | + | 
 | 1191 | +        def m2m(data, model_cls):  | 
 | 1192 | +            ids = []  | 
 | 1193 | +            for item in data:  | 
 | 1194 | +                if isinstance(item, dict):  | 
 | 1195 | +                    item_id = item.get("id")  | 
 | 1196 | +                    if item_id:  | 
 | 1197 | +                        ids.append(item_id)  | 
 | 1198 | +                    else:  | 
 | 1199 | +                        obj = create_with_m2m(item, model_cls)  | 
 | 1200 | +                        ids.append(obj.id)  | 
 | 1201 | +                else:  | 
 | 1202 | +                    ids.append(item)  | 
 | 1203 | +            return ids  | 
 | 1204 | + | 
 | 1205 | +        # planned_interventions M2M handling  | 
 | 1206 | +        planned_interventions_data = self.initial_data.get("planned_interventions")  | 
 | 1207 | +        if planned_interventions_data:  | 
 | 1208 | +            operational_update.planned_interventions.set(m2m(planned_interventions_data, PlannedIntervention))  | 
 | 1209 | + | 
 | 1210 | +        # needs_identified M2M handling  | 
 | 1211 | +        needs_identified_data = self.initial_data.get("needs_identified")  | 
 | 1212 | +        if needs_identified_data:  | 
 | 1213 | +            operational_update.needs_identified.set(m2m(needs_identified_data, IdentifiedNeed))  | 
 | 1214 | + | 
 | 1215 | +        # national_society_actions M2M handling  | 
 | 1216 | +        national_society_actions_data = self.initial_data.get("national_society_actions")  | 
 | 1217 | +        if national_society_actions_data:  | 
 | 1218 | +            operational_update.national_society_actions.set(m2m(national_society_actions_data, NationalSocietyAction))  | 
 | 1219 | + | 
 | 1220 | +        # risk_security M2M handling  | 
 | 1221 | +        risk_security_data = self.initial_data.get("risk_security")  | 
 | 1222 | +        if risk_security_data:  | 
 | 1223 | +            operational_update.risk_security.set(m2m(risk_security_data, RiskSecurity))  | 
 | 1224 | + | 
 | 1225 | +        # source_information M2M handling  | 
 | 1226 | +        source_information_data = self.initial_data.get("source_information")  | 
 | 1227 | +        if source_information_data:  | 
 | 1228 | +            operational_update.source_information.set(m2m(source_information_data, SourceInformation))  | 
 | 1229 | + | 
 | 1230 | +        # proposed_action M2M handling  | 
 | 1231 | +        proposed_action_data = self.initial_data.get("proposed_action")  | 
 | 1232 | +        if proposed_action_data:  | 
 | 1233 | +            if isinstance(proposed_action_data, str):  | 
 | 1234 | +                proposed_action_data = ev(proposed_action_data)  | 
 | 1235 | +            if isinstance(proposed_action_data, dict):  | 
 | 1236 | +                proposed_action_data = [proposed_action_data]  | 
 | 1237 | +            operational_update.proposed_action.set(m2m(proposed_action_data, ProposedAction))  | 
 | 1238 | + | 
 | 1239 | +        return operational_update  | 
1083 | 1240 | 
 
  | 
1084 | 1241 | 
 
  | 
1085 | 1242 | class DrefFinalReportSerializer(NestedUpdateMixin, NestedCreateMixin, ModelSerializer):  | 
@@ -1520,7 +1677,81 @@ def update(self, instance, validated_data):  | 
1520 | 1677 |             raise serializers.ValidationError({"modified_at": settings.DREF_OP_UPDATE_FINAL_REPORT_UPDATE_ERROR_MESSAGE})  | 
1521 | 1678 |         validated_data["modified_at"] = timezone.now()  | 
1522 | 1679 |         validated_data["modified_by"] = self.context["request"].user  | 
1523 |  | -        return super().update(instance, validated_data)  | 
 | 1680 | +        final_report = super().update(instance, validated_data)  | 
 | 1681 | + | 
 | 1682 | +        # Helpers for robust M2M update  | 
 | 1683 | + | 
 | 1684 | +        def create_with_m2m(item, model_cls):  | 
 | 1685 | +            # Only handle PlannedIntervention/indicators for now  | 
 | 1686 | +            m2m_fields = []  | 
 | 1687 | +            if model_cls.__name__ == "PlannedIntervention" and "indicators" in item:  | 
 | 1688 | +                m2m_fields = ["indicators"]  | 
 | 1689 | +            # Remove m2m fields from item for create  | 
 | 1690 | +            item_clean = {k: v for k, v in item.items() if k not in m2m_fields}  | 
 | 1691 | +            obj = model_cls.objects.create(**item_clean)  | 
 | 1692 | +            # Set m2m fields after create  | 
 | 1693 | +            for m2m_field in m2m_fields:  | 
 | 1694 | +                values = item[m2m_field]  | 
 | 1695 | +                rel_model = obj._meta.get_field(m2m_field).related_model  | 
 | 1696 | +                rel_ids = []  | 
 | 1697 | +                for v in values:  | 
 | 1698 | +                    if isinstance(v, dict):  | 
 | 1699 | +                        rel_obj = rel_model.objects.create(**v)  | 
 | 1700 | +                        rel_ids.append(rel_obj.id)  | 
 | 1701 | +                    else:  | 
 | 1702 | +                        rel_ids.append(v)  | 
 | 1703 | +                getattr(obj, m2m_field).set(rel_ids)  | 
 | 1704 | +            return obj  | 
 | 1705 | + | 
 | 1706 | +        def m2m(data, model_cls):  | 
 | 1707 | +            ids = []  | 
 | 1708 | +            for item in data:  | 
 | 1709 | +                if isinstance(item, dict):  | 
 | 1710 | +                    item_id = item.get("id")  | 
 | 1711 | +                    if item_id:  | 
 | 1712 | +                        ids.append(item_id)  | 
 | 1713 | +                    else:  | 
 | 1714 | +                        obj = create_with_m2m(item, model_cls)  | 
 | 1715 | +                        ids.append(obj.id)  | 
 | 1716 | +                else:  | 
 | 1717 | +                    ids.append(item)  | 
 | 1718 | +            return ids  | 
 | 1719 | + | 
 | 1720 | +        # planned_interventions M2M handling  | 
 | 1721 | +        planned_interventions_data = self.initial_data.get("planned_interventions")  | 
 | 1722 | +        if planned_interventions_data:  | 
 | 1723 | +            final_report.planned_interventions.set(m2m(planned_interventions_data, PlannedIntervention))  | 
 | 1724 | + | 
 | 1725 | +        # needs_identified M2M handling  | 
 | 1726 | +        needs_identified_data = self.initial_data.get("needs_identified")  | 
 | 1727 | +        if needs_identified_data:  | 
 | 1728 | +            final_report.needs_identified.set(m2m(needs_identified_data, IdentifiedNeed))  | 
 | 1729 | + | 
 | 1730 | +        # national_society_actions M2M handling  | 
 | 1731 | +        national_society_actions_data = self.initial_data.get("national_society_actions")  | 
 | 1732 | +        if national_society_actions_data:  | 
 | 1733 | +            final_report.national_society_actions.set(m2m(national_society_actions_data, NationalSocietyAction))  | 
 | 1734 | + | 
 | 1735 | +        # risk_security M2M handling  | 
 | 1736 | +        risk_security_data = self.initial_data.get("risk_security")  | 
 | 1737 | +        if risk_security_data:  | 
 | 1738 | +            final_report.risk_security.set(m2m(risk_security_data, RiskSecurity))  | 
 | 1739 | + | 
 | 1740 | +        # source_information M2M handling  | 
 | 1741 | +        source_information_data = self.initial_data.get("source_information")  | 
 | 1742 | +        if source_information_data:  | 
 | 1743 | +            final_report.source_information.set(m2m(source_information_data, SourceInformation))  | 
 | 1744 | + | 
 | 1745 | +        # proposed_action M2M handling  | 
 | 1746 | +        proposed_action_data = self.initial_data.get("proposed_action")  | 
 | 1747 | +        if proposed_action_data:  | 
 | 1748 | +            if isinstance(proposed_action_data, str):  | 
 | 1749 | +                proposed_action_data = ev(proposed_action_data)  | 
 | 1750 | +            if isinstance(proposed_action_data, dict):  | 
 | 1751 | +                proposed_action_data = [proposed_action_data]  | 
 | 1752 | +            final_report.proposed_action.set(m2m(proposed_action_data, ProposedAction))  | 
 | 1753 | + | 
 | 1754 | +        return final_report  | 
1524 | 1755 | 
 
  | 
1525 | 1756 | 
 
  | 
1526 | 1757 | class CompletedDrefOperationsSerializer(serializers.ModelSerializer):  | 
 | 
0 commit comments