Skip to content

Commit 8019d68

Browse files
committed
[change] Make Notification.type non-nullable #481
- Removed null=True from AbstractNotification.type field - Made type required in notify.send() (kwargs.pop without default) - Removed type=None guard logic in _get_related_object_url, get_message, email_subject, notify_handler, and get_user_email_preference - Updated tests to always pass type='default' - Updated docs to document type as required - Created migration 0013_make_notification_type_nonnullable
1 parent f591b4e commit 8019d68

8 files changed

Lines changed: 110 additions & 62 deletions

File tree

docs/developer/sending-notifications.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ The complete syntax for ``notify`` is:
5252
.. code-block:: python
5353
5454
notify.send(
55-
actor,
55+
sender,
56+
type,
5657
recipient,
5758
verb,
5859
action_object,
@@ -87,8 +88,7 @@ The ``notify`` signal supports the following parameters:
8788
``type`` Set values of other parameters based on registered
8889
:doc:`notification types <./notification-types>`
8990

90-
Defaults to ``None`` meaning you need to provide other
91-
arguments.
91+
This parameter is required.
9292
``email_subject`` Sets subject of email notification to be sent.
9393

9494
Defaults to the notification message.

openwisp_notifications/base/models.py

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ class AbstractNotification(UUIDModel, BaseNotification):
8484
CACHE_KEY_PREFIX = "ow-notifications-"
8585
type = models.CharField(
8686
max_length=30,
87-
null=True,
8887
# TODO: Remove when dropping support for Django 4.2
8988
choices=(
9089
NOTIFICATION_CHOICES
@@ -216,16 +215,15 @@ def _get_related_object_url(self, field):
216215
"""
217216
Returns URLs for "actor", "action_object" and "target" fields.
218217
"""
219-
if self.type:
220-
# Generate URL according to the notification configuration
221-
config = get_notification_configuration(self.type)
222-
url = config.get(f"{field}_link", None)
223-
if url:
224-
try:
225-
url_callable = import_string(url)
226-
return url_callable(self, field=field, absolute_url=True)
227-
except ImportError:
228-
return url
218+
# Generate URL according to the notification configuration
219+
config = get_notification_configuration(self.type)
220+
url = config.get(f"{field}_link", None)
221+
if url:
222+
try:
223+
url_callable = import_string(url)
224+
return url_callable(self, field=field, absolute_url=True)
225+
except ImportError:
226+
return url
229227
return _get_object_link(obj=self._related_object(field), absolute_url=True)
230228

231229
@property
@@ -260,8 +258,6 @@ def email_message(self):
260258
return self.get_message()
261259

262260
def get_message(self):
263-
if not self.type:
264-
return self.description
265261
try:
266262
config = get_notification_configuration(self.type)
267263
data = self.data or {}
@@ -283,23 +279,18 @@ def get_message(self):
283279

284280
@cached_property
285281
def email_subject(self):
286-
if self.type:
287-
try:
288-
config = get_notification_configuration(self.type)
289-
data = self.data or {}
290-
return config["email_subject"].format(
291-
site=Site.objects.get_current(), notification=self, **data
292-
)
293-
except (AttributeError, KeyError, NotificationRenderException) as exception:
294-
self._invalid_notification(
295-
self.pk,
296-
exception,
297-
"Error encountered in generating notification email",
298-
)
299-
elif self.data.get("email_subject", None):
300-
return self.data.get("email_subject")
301-
else:
302-
return self.message
282+
try:
283+
config = get_notification_configuration(self.type)
284+
data = self.data or {}
285+
return config["email_subject"].format(
286+
site=Site.objects.get_current(), notification=self, **data
287+
)
288+
except (AttributeError, KeyError, NotificationRenderException) as exception:
289+
self._invalid_notification(
290+
self.pk,
291+
exception,
292+
"Error encountered in generating notification email",
293+
)
303294

304295
def _related_object(self, field):
305296
obj_id = getattr(self, f"{field}_object_id")

openwisp_notifications/handlers.py

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def notify_handler(**kwargs):
5454
description = kwargs.pop("description", None)
5555
timestamp = kwargs.pop("timestamp", timezone.now())
5656
recipient = kwargs.pop("recipient", None)
57-
notification_type = kwargs.pop("type", None)
57+
notification_type = kwargs.pop("type")
5858
target = kwargs.get("target", None)
5959
target_org = getattr(target, "organization_id", None)
6060
try:
@@ -88,33 +88,32 @@ def notify_handler(**kwargs):
8888
# chain can still resolve to True. Org-level web settings
8989
# are resolved via JOINs in the main query to avoid an
9090
# additional OrganizationNotificationSettings lookup.
91-
if notification_type:
92-
web_notification = Q(notificationsetting__web=True)
93-
if notification_template["web_notification"]:
94-
# Users with web=None inherit the org setting, so
95-
# include them unless the org explicitly disables
96-
# web notifications.
97-
web_notification |= Q(
98-
notificationsetting__web=None,
99-
) & (
100-
Q(
101-
notificationsetting__organization__notification_settings__web=True
102-
)
103-
| Q(
104-
notificationsetting__organization__notification_settings__web=None
105-
)
106-
| Q(
107-
notificationsetting__organization__notification_settings__isnull=True
108-
)
91+
web_notification = Q(notificationsetting__web=True)
92+
if notification_template["web_notification"]:
93+
# Users with web=None inherit the org setting, so
94+
# include them unless the org explicitly disables
95+
# web notifications.
96+
web_notification |= Q(
97+
notificationsetting__web=None,
98+
) & (
99+
Q(
100+
notificationsetting__organization__notification_settings__web=True
101+
)
102+
| Q(
103+
notificationsetting__organization__notification_settings__web=None
104+
)
105+
| Q(
106+
notificationsetting__organization__notification_settings__isnull=True
109107
)
110-
111-
notification_setting = web_notification & Q(
112-
notificationsetting__type=notification_type,
113-
notificationsetting__organization_id=target_org,
114-
notificationsetting__deleted=False,
115108
)
116-
where = where & notification_setting
117-
where_group = where_group & notification_setting
109+
110+
notification_setting = web_notification & Q(
111+
notificationsetting__type=notification_type,
112+
notificationsetting__organization_id=target_org,
113+
notificationsetting__deleted=False,
114+
)
115+
where = where & notification_setting
116+
where_group = where_group & notification_setting
118117

119118
# Ensure notifications are only sent to active user
120119
where = where & Q(is_active=True)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import django
2+
from django.db import migrations, models
3+
4+
from openwisp_notifications.types import NOTIFICATION_CHOICES, get_notification_choices
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("openwisp_notifications", "0012_replace_jsonfield_with_django_builtin"),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name="notification",
16+
name="type",
17+
field=models.CharField(
18+
choices=(
19+
NOTIFICATION_CHOICES
20+
if django.VERSION < (5, 0)
21+
else get_notification_choices
22+
),
23+
max_length=30,
24+
verbose_name="Notification Type",
25+
),
26+
),
27+
]

openwisp_notifications/tests/test_admin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def setUp(self):
6363
description="Test Notification",
6464
verb="Test Notification",
6565
email_subject="Test Email subject",
66+
type="default",
6667
url="localhost:8000/admin",
6768
)
6869
self.site = AdminSite()

openwisp_notifications/tests/test_notifications.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ def setUp(self):
7777
description="Test Notification",
7878
verb="Test Notification",
7979
email_subject="Test Email subject",
80+
type="default",
8081
url="https://localhost:8000/admin",
8182
)
8283

@@ -93,6 +94,7 @@ def test_create_notification(self):
9394
recipient=self.admin,
9495
description="Test Notification Description",
9596
verb="Test Notification",
97+
type="default",
9698
action_object=operator,
9799
target=operator,
98100
data=data,
@@ -126,6 +128,7 @@ def test_lazy_translation(self):
126128
description=gettext_lazy("Test Notification"),
127129
verb=gettext_lazy("Test Notification"),
128130
email_subject=gettext_lazy("Test Email subject"),
131+
type="default",
129132
url="https://localhost:8000/admin",
130133
message=gettext_lazy("Translated message"),
131134
random=gettext_lazy("any extra kwargs is evaluated"),
@@ -1983,6 +1986,7 @@ def setUp(self):
19831986
level="info",
19841987
verb="Test Notification",
19851988
email_subject="Test Email subject",
1989+
type="default",
19861990
url="https://localhost:8000/admin",
19871991
)
19881992

openwisp_notifications/utils.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,8 @@ def get_user_email_preference(notification):
161161
If the user has no preference set, it defaults to True.
162162
"""
163163
target_org = getattr(getattr(notification, "target", None), "organization_id", None)
164-
if not (notification.type and target_org):
165-
# We can not check email preference if notification type is absent,
166-
# or if target_org is not present
164+
if not target_org:
165+
# We can not check email preference if target_org is not present,
167166
# therefore send email anyway.
168167
return True
169168
try:
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import django
2+
from django.db import migrations, models
3+
4+
from openwisp_notifications.types import NOTIFICATION_CHOICES, get_notification_choices
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("sample_notifications", "0004_replace_jsonfield_with_django_builtin"),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name="notification",
16+
name="type",
17+
field=models.CharField(
18+
choices=(
19+
NOTIFICATION_CHOICES
20+
if django.VERSION < (5, 0)
21+
else get_notification_choices
22+
),
23+
max_length=30,
24+
verbose_name="Notification Type",
25+
),
26+
),
27+
]

0 commit comments

Comments
 (0)