|  | 
| 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