Skip to content

Commit 9009484

Browse files
committed
[fix] Resync org settings on repeated global email disable #431
Preserve explicit global preference updates from the preferences API so reapplying the same global state still propagates to organization notification settings, add API regression coverage for repeated global email and web updates, and include the CI-requested formatting fix. Fixes #431
1 parent d5b5bb9 commit 9009484

4 files changed

Lines changed: 121 additions & 7 deletions

File tree

openwisp_notifications/api/serializers.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ def to_representation(self, instance):
9191
data["email"] = instance.email_notification
9292
return data
9393

94+
def update(self, instance, validated_data):
95+
explicit_global_fields = {
96+
field for field in ["web", "email"] if field in validated_data
97+
}
98+
if explicit_global_fields and not instance.organization and not instance.type:
99+
# The preferences UI can explicitly reapply the same global state.
100+
# Preserve that intent so the model can resync organization rows.
101+
instance._explicit_global_update_fields = explicit_global_fields
102+
return super().update(instance, validated_data)
103+
94104

95105
class IgnoreObjectNotificationSerializer(serializers.ModelSerializer):
96106
class Meta:

openwisp_notifications/base/models.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -445,15 +445,26 @@ def validate_global_setting(self):
445445
raise ValidationError("There can only be one global setting per user.")
446446

447447
def save(self, *args, **kwargs):
448+
explicit_global_update_fields = set(
449+
getattr(self, "_explicit_global_update_fields", [])
450+
)
451+
if hasattr(self, "_explicit_global_update_fields"):
452+
delattr(self, "_explicit_global_update_fields")
448453
if not self.web_notification:
449454
self.email = self.web_notification
450455
with transaction.atomic():
451456
if not self.organization and not self.type:
452457
try:
453-
previous_state = self.__class__.objects.only("email").get(
458+
previous_state = self.__class__.objects.only("web", "email").get(
454459
pk=self.pk
455460
)
456-
updates = {"web": self.web}
461+
updates = {}
462+
463+
if (
464+
self.web != previous_state.web
465+
or "web" in explicit_global_update_fields
466+
):
467+
updates["web"] = self.web
457468

458469
# If global web notifications are disabled, then disable email notifications as well
459470
if not self.web:
@@ -462,7 +473,10 @@ def save(self, *args, **kwargs):
462473
# Update email notifiations only if it's different from the previous state
463474
# Otherwise, it would overwrite the email notification settings for specific
464475
# setting that were enabled by the user after disabling global email notifications
465-
if self.email != previous_state.email:
476+
if (
477+
self.email != previous_state.email
478+
or "email" in explicit_global_update_fields
479+
):
466480
updates["email"] = self.email
467481

468482
self.user.notificationsetting_set.exclude(pk=self.pk).update(

openwisp_notifications/tests/test_api.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,98 @@ def test_update_notification_setting_api(self):
794794
)
795795
self.assertEqual(response.status_code, 403)
796796

797+
def test_reapplying_global_email_setting_updates_organization_settings(self):
798+
org = self._get_org("default")
799+
global_setting = NotificationSetting.objects.get(
800+
user=self.admin, type=None, organization=None
801+
)
802+
global_setting_url = self._get_path("notification_setting", global_setting.pk)
803+
org_setting_url = self._get_path(
804+
"user_org_notification_setting", self.admin.pk, org.pk
805+
)
806+
807+
response = self.client.patch(
808+
global_setting_url,
809+
data={"email": False},
810+
content_type="application/json",
811+
)
812+
self.assertEqual(response.status_code, 200)
813+
self.assertFalse(
814+
NotificationSetting.objects.filter(
815+
user=self.admin, organization=org, email=True
816+
).exists()
817+
)
818+
819+
response = self.client.post(
820+
org_setting_url,
821+
data={"web": True, "email": True},
822+
content_type="application/json",
823+
)
824+
self.assertEqual(response.status_code, 200)
825+
self.assertTrue(
826+
NotificationSetting.objects.filter(
827+
user=self.admin, organization=org, email=True
828+
).exists()
829+
)
830+
831+
response = self.client.patch(
832+
global_setting_url,
833+
data={"email": False},
834+
content_type="application/json",
835+
)
836+
self.assertEqual(response.status_code, 200)
837+
self.assertFalse(
838+
NotificationSetting.objects.filter(
839+
user=self.admin, organization=org, email=True
840+
).exists()
841+
)
842+
843+
def test_reapplying_global_web_setting_updates_organization_settings(self):
844+
org = self._get_org("default")
845+
global_setting = NotificationSetting.objects.get(
846+
user=self.admin, type=None, organization=None
847+
)
848+
global_setting_url = self._get_path("notification_setting", global_setting.pk)
849+
org_setting_url = self._get_path(
850+
"user_org_notification_setting", self.admin.pk, org.pk
851+
)
852+
853+
response = self.client.patch(
854+
global_setting_url,
855+
data={"web": False},
856+
content_type="application/json",
857+
)
858+
self.assertEqual(response.status_code, 200)
859+
self.assertFalse(
860+
NotificationSetting.objects.filter(
861+
user=self.admin, organization=org, web=True
862+
).exists()
863+
)
864+
865+
response = self.client.post(
866+
org_setting_url,
867+
data={"web": True, "email": False},
868+
content_type="application/json",
869+
)
870+
self.assertEqual(response.status_code, 200)
871+
self.assertTrue(
872+
NotificationSetting.objects.filter(
873+
user=self.admin, organization=org, web=True
874+
).exists()
875+
)
876+
877+
response = self.client.patch(
878+
global_setting_url,
879+
data={"web": False},
880+
content_type="application/json",
881+
)
882+
self.assertEqual(response.status_code, 200)
883+
self.assertFalse(
884+
NotificationSetting.objects.filter(
885+
user=self.admin, organization=org, web=True
886+
).exists()
887+
)
888+
797889
def test_disallowed_change_types_absent_in_notification_setting_api(self):
798890
with self.subTest("disallowed type setting not present in list"):
799891
path = self._get_path("notification_setting_list")

openwisp_notifications/tests/test_selenium.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,13 +171,11 @@ def test_email_unsubscribe_page(self):
171171

172172
with self.subTest("Network request fails"):
173173
self.open(unsubscribe_link)
174-
self.web_driver.execute_script(
175-
"""
174+
self.web_driver.execute_script("""
176175
window.fetch = function() {
177176
return Promise.reject(new Error('Simulated fetch failure'));
178177
};
179-
"""
180-
)
178+
""")
181179
self.web_driver.find_element(By.ID, "toggle-btn").click()
182180
self.wait_for_visibility(By.ID, "error-msg")
183181
browser_logs = self.get_browser_logs()

0 commit comments

Comments
 (0)