Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Implement global notification setting for email/web #284

Open
wants to merge 5 commits into
base: gsoc24
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions openwisp_notifications/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,9 @@ def get_api_urls(api_views=None):
views.ignore_object_notification,
name='ignore_object_notification',
),
path(
'preference/',
views.notification_preference,
name='notification_preference',
),
]
27 changes: 27 additions & 0 deletions openwisp_notifications/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,32 @@ def perform_create(self, serializer):
)


class NotificationPreferenceView(BaseNotificationView):
def post(self, request):
data = request.data

email = data.get('email')
web = data.get('web')

if not isinstance(email, bool) or not isinstance(web, bool):
return Response({'error': 'Invalid values for email or web'}, status=400)

notification_settings, created = NotificationSetting.objects.update_or_create(
user=request.user,
organization=None,
type=None,
defaults={'email': email, 'web': web},
)

return Response(
{
'success': True,
'email': notification_settings.email,
'web': notification_settings.web,
}
)


notifications_list = NotificationListView.as_view()
notification_detail = NotificationDetailView.as_view()
notifications_read_all = NotificationReadAllView.as_view()
Expand All @@ -206,3 +232,4 @@ def perform_create(self, serializer):
notification_setting = NotificationSettingView.as_view()
ignore_object_notification_list = IgnoreObjectNotificationListView.as_view()
ignore_object_notification = IgnoreObjectNotificationView.as_view()
notification_preference = NotificationPreferenceView.as_view()
2 changes: 2 additions & 0 deletions openwisp_notifications/base/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ class AbstractNotificationSetting(UUIDModel):
organization = models.ForeignKey(
get_model_name('openwisp_users', 'Organization'),
on_delete=models.CASCADE,
null=True,
blank=True,
)
web = models.BooleanField(
_('web notifications'), null=True, blank=True, help_text=_(_RECEIVE_HELP)
Expand Down
40 changes: 26 additions & 14 deletions openwisp_notifications/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,15 @@ def notify_handler(**kwargs):
notificationsetting__organization_id=target_org,
notificationsetting__deleted=False,
)
where = where & notification_setting
where_group = where_group & notification_setting

global_setting = web_notification & Q(
notificationsetting__type=None,
notificationsetting__organization_id=None,
notificationsetting__deleted=False,
)

where = where & (notification_setting | global_setting)
where_group = where_group & (notification_setting | global_setting)

# Ensure notifications are only sent to active user
where = where & Q(is_active=True)
Expand Down Expand Up @@ -171,20 +178,25 @@ def send_email_notification(sender, instance, created, **kwargs):
return
# Get email preference of user for this type of notification.
target_org = getattr(getattr(instance, 'target', None), 'organization_id', None)
notification_setting = None
if instance.type and target_org:
try:
notification_setting = instance.recipient.notificationsetting_set.get(
organization=target_org, type=instance.type
)
except NotificationSetting.DoesNotExist:
return
email_preference = notification_setting.email_notification
else:
# We can not check email preference if notification type is absent,
# or if target_org is not present
# therefore send email anyway.
email_preference = True
# Check for specific notification setting for the target organization and type
notification_setting = instance.recipient.notificationsetting_set.filter(
organization=target_org, type=instance.type
).first()
if not notification_setting:
# Check for global notification setting
notification_setting = instance.recipient.notificationsetting_set.filter(
organization=None, type=None
).first()

if instance.type and target_org and not notification_setting:
return

# Send email anyway if no notification setting
email_preference = (
notification_setting.email_notification if notification_setting else True
)
email_verified = instance.recipient.emailaddress_set.filter(
verified=True, email=instance.recipient.email
).exists()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 4.2.11 on 2024-06-18 13:05

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("openwisp_users", "0020_populate_password_updated_field"),
("openwisp_notifications", "0007_notificationsetting_deleted"),
]

operations = [
migrations.AlterField(
model_name="notificationsetting",
name="organization",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="openwisp_users.organization",
),
),
]
22 changes: 22 additions & 0 deletions openwisp_notifications/tests/test_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,28 @@ def test_notification_type_web_notification_setting_true(self):
self._create_notification()
self.assertEqual(notification_queryset.count(), 0)

@mock_notification_types
def test_global_email_notification_setting(self):
with self.subTest('Test email global preference is "False"'):
NotificationSetting.objects.update_or_create(
user=self.admin,
organization=None,
type=None,
defaults={'email': False, 'web': False},
)
self._create_notification()
self.assertEqual(len(mail.outbox), 0)

with self.subTest('Test email global preference is "True"'):
NotificationSetting.objects.update_or_create(
user=self.admin,
organization=None,
type=None,
defaults={'email': True, 'web': True},
)
self._create_notification()
self.assertEqual(len(mail.outbox), 1)

@mock_notification_types
def test_notification_type_web_notification_setting_false(self):
target_obj = self._get_org_user()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 4.2.11 on 2024-06-20 13:02

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("openwisp_users", "0020_populate_password_updated_field"),
("sample_notifications", "0002_testapp"),
]

operations = [
migrations.AlterField(
model_name="notificationsetting",
name="organization",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="openwisp_users.organization",
),
),
]
Loading