From 4583ef3186350386078a5f09ae8aefbd30a86cfd Mon Sep 17 00:00:00 2001 From: Sushil Tiwari Date: Fri, 22 May 2026 10:33:24 +0545 Subject: [PATCH] fix(eap): return non-field error for duplicate eap registration - update on email notification --- assets | 2 +- ...ulleap_unique_full_eap_version_and_more.py | 36 +++++++++++++++++++ eap/models.py | 6 ++++ eap/serializers.py | 33 +++++++++++------ notifications/notification.py | 3 ++ 5 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 eap/migrations/0005_remove_fulleap_unique_full_eap_version_and_more.py diff --git a/assets b/assets index 9758a5685..c5f3d1b31 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 9758a56851025cfa38023baf5466b743c522a653 +Subproject commit c5f3d1b319bdb8ee13ff3c3d1aa10616cec4c517 diff --git a/eap/migrations/0005_remove_fulleap_unique_full_eap_version_and_more.py b/eap/migrations/0005_remove_fulleap_unique_full_eap_version_and_more.py new file mode 100644 index 000000000..96c42eb4a --- /dev/null +++ b/eap/migrations/0005_remove_fulleap_unique_full_eap_version_and_more.py @@ -0,0 +1,36 @@ +# Generated by Django 4.2.30 on 2026-05-22 03:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("eap", "0004_emailrecipient_and_more"), + ] + + operations = [ + migrations.RemoveConstraint( + model_name="fulleap", + name="unique_full_eap_version", + ), + migrations.RemoveConstraint( + model_name="simplifiedeap", + name="unique_simplified_eap_version", + ), + migrations.AddConstraint( + model_name="fulleap", + constraint=models.UniqueConstraint( + fields=("eap_registration", "version"), + name="unique_full_eap_version", + violation_error_message="A Full EAP for this EAP Registration with this version already exists.", + ), + ), + migrations.AddConstraint( + model_name="simplifiedeap", + constraint=models.UniqueConstraint( + fields=("eap_registration", "version"), + name="unique_simplified_eap_version", + violation_error_message="A Simplified EAP for this EAP Registration with this version already exists.", + ), + ), + ] diff --git a/eap/models.py b/eap/models.py index 0c98fba00..26c5fdd1b 100644 --- a/eap/models.py +++ b/eap/models.py @@ -1331,6 +1331,9 @@ class Meta: models.UniqueConstraint( fields=["eap_registration", "version"], name="unique_simplified_eap_version", + violation_error_message=_( + "A Simplified EAP for this EAP Registration with this version already exists.", + ), ) ] @@ -1880,6 +1883,9 @@ class Meta: models.UniqueConstraint( fields=["eap_registration", "version"], name="unique_full_eap_version", + violation_error_message=_( + "A Full EAP for this EAP Registration with this version already exists.", + ), ) ] diff --git a/eap/serializers.py b/eap/serializers.py index 905241be9..9ebfa3e4a 100644 --- a/eap/serializers.py +++ b/eap/serializers.py @@ -9,6 +9,7 @@ from django.utils.translation import gettext from rest_framework import serializers from rest_framework.exceptions import PermissionDenied +from rest_framework.validators import UniqueTogetherValidator from api.serializers import ( Admin2Serializer, @@ -557,6 +558,7 @@ class SimplifiedEAPSerializer( ): # FILES + version = serializers.IntegerField(default=1, read_only=True) hazard_impact_images = EAPFileUpdateSerializer(required=False, many=True) selected_early_actions_images = EAPFileUpdateSerializer(required=False, many=True, allow_null=True) risk_selected_protocols_images = EAPFileUpdateSerializer(required=False, many=True, allow_null=True) @@ -585,6 +587,13 @@ class Meta: "created_by", "modified_by", ] + validators = [ + UniqueTogetherValidator( + queryset=SimplifiedEAP.objects.all(), + fields=["eap_registration", "version"], + message="EAP for this registration has already been created.", + ) + ] exclude = ("cover_image",) def _validate_timeframe(self, data: dict[str, typing.Any]) -> None: @@ -636,11 +645,6 @@ def _validate_timeframe(self, data: dict[str, typing.Any]) -> None: {"operational_timeframe": gettext("operational timeframe value is not valid for Months unit.")} ) - def validate_eap_registration(self, eap_registration: EAPRegistration) -> EAPRegistration: - if not self.instance and eap_registration.has_eap_application: - raise serializers.ValidationError("EAP for this registration has already been created.") - return eap_registration - def validate(self, data: dict[str, typing.Any]) -> dict[str, typing.Any]: original_eap_registration = getattr(self.instance, "eap_registration", None) if self.instance else None eap_registration: EAPRegistration | None = data.get("eap_registration", original_eap_registration) @@ -649,6 +653,9 @@ def validate(self, data: dict[str, typing.Any]) -> dict[str, typing.Any]: if self.instance and original_eap_registration != eap_registration: raise serializers.ValidationError("EAP Registration cannot be changed for existing EAP.") + if not self.instance and eap_registration.has_eap_application: + raise serializers.ValidationError(gettext("EAP for this registration has already been created.")) + if self.instance and eap_registration.get_status_enum not in [ EAPRegistration.Status.UNDER_DEVELOPMENT, EAPRegistration.Status.NS_ADDRESSING_COMMENTS, @@ -690,6 +697,7 @@ class FullEAPSerializer( CommonEAPFieldsSerializer, ): + version = serializers.IntegerField(default=1, read_only=True) # admins key_actors = KeyActorSerializer(many=True, required=True) @@ -795,6 +803,13 @@ class Meta: "created_by", "modified_by", ] + validators = [ + UniqueTogetherValidator( + queryset=FullEAP.objects.all(), + fields=["eap_registration", "version"], + message="EAP for this registration has already been created.", + ) + ] exclude = ("cover_image",) def _validate_timeframe(self, data: dict[str, typing.Any]) -> None: @@ -811,11 +826,6 @@ def _validate_timeframe(self, data: dict[str, typing.Any]) -> None: if lead_unit is not None and lead_time_value is not None and lead_unit != TimeFrame.DAYS: raise serializers.ValidationError({"lead_timeframe_unit": gettext("lead timeframe unit must be Days for Full EAP.")}) - def validate_eap_registration(self, eap_registration: EAPRegistration) -> EAPRegistration: - if not self.instance and eap_registration.has_eap_application: - raise serializers.ValidationError("EAP for this registration has already been created.") - return eap_registration - def validate(self, data: dict[str, typing.Any]) -> dict[str, typing.Any]: original_eap_registration = getattr(self.instance, "eap_registration", None) if self.instance else None eap_registration: EAPRegistration | None = data.get("eap_registration", original_eap_registration) @@ -824,6 +834,9 @@ def validate(self, data: dict[str, typing.Any]) -> dict[str, typing.Any]: if self.instance and original_eap_registration != eap_registration: raise serializers.ValidationError("EAP Registration cannot be changed for existing EAP.") + if not self.instance and eap_registration.has_eap_application: + raise serializers.ValidationError(gettext("EAP for this registration has already been created.")) + if self.instance and eap_registration.get_status_enum not in [ EAPRegistration.Status.UNDER_DEVELOPMENT, EAPRegistration.Status.NS_ADDRESSING_COMMENTS, diff --git a/notifications/notification.py b/notifications/notification.py index 3e240e719..3ee467809 100644 --- a/notifications/notification.py +++ b/notifications/notification.py @@ -88,6 +88,9 @@ def send_notification(subject, recipients, html, mailtype="", cc_recipients=None print("-" * 22, "EMAIL END -", "-" * 22) return + recipients = recipients if isinstance(recipients, list) else [recipients] + cc_recipients = cc_recipients if isinstance(cc_recipients, list) else [cc_recipients] + to_addresses = clean_emails(recipients) cc_addresses = clean_emails(cc_recipients) addresses = to_addresses + cc_addresses