Skip to content

Commit 2f44f01

Browse files
committed
Allow rendering to work without saving the notification into DB
1 parent f47a413 commit 2f44f01

File tree

6 files changed

+227
-110
lines changed

6 files changed

+227
-110
lines changed

HISTORY.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ not released yet
66
----------------
77

88
* Fix documentation
9+
* Allow rendering to work without saving the notification into DB
910

1011
0.2.2 (2020-02-11)
1112
------------------

example/tests/test_handlers.py

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from pynotify.dispatchers import BaseDispatcher
1010
from pynotify.handlers import BaseHandler
1111
from pynotify.helpers import signal_map
12-
from pynotify.models import AdminNotificationTemplate
12+
from pynotify.models import AdminNotificationTemplate, Notification
1313

1414

1515
# MOCK OBJECTS ------------------------------------------------------------------------------------
@@ -54,14 +54,20 @@ def get_related_objects(self):
5454
def get_extra_data(self):
5555
return {'some_value': 123}
5656

57+
def _can_handle(self):
58+
return super()._can_handle() and self.signal_kwargs.get('can_handle', True)
59+
5760
def _can_create_notification(self, recipient):
58-
return recipient.username != 'James'
61+
return super()._can_create_notification(recipient) and self.signal_kwargs.get('can_create', True)
5962

60-
def _can_dispatch_notification(self, notification, dispatcher):
61-
return notification.recipient.username != 'John'
63+
def _can_save_notification(self, notification):
64+
return super()._can_save_notification(notification) and self.signal_kwargs.get('can_save', True)
6265

63-
def _can_handle(self):
64-
return super()._can_handle() and self.signal_kwargs.get('can_handle', True)
66+
def _can_dispatch_notification(self, notification, dispatcher):
67+
return (
68+
super()._can_dispatch_notification(notification, dispatcher)
69+
and self.signal_kwargs.get('can_dispatch', True)
70+
)
6571

6672
class Meta:
6773
signal = test_signal_data
@@ -87,7 +93,6 @@ class HandlerTestCase(TestCase):
8793
def setUp(self):
8894
self.user1 = User.objects.create_user('Jack')
8995
self.user2 = User.objects.create_user('John')
90-
self.user3 = User.objects.create_user('James')
9196
self.template = AdminNotificationTemplate.objects.create(title='Hello slug!', slug='test_slug')
9297

9398
def test_handler_should_be_automatically_registered(self):
@@ -123,7 +128,10 @@ class Meta:
123128

124129
def test_handler_should_create_notification_using_template_data(self):
125130
users = [self.user1, self.user2]
126-
test_signal_data.send(sender=None, recipients=[self.user1, self.user2, self.user3])
131+
test_signal_data.send(sender=None, recipients=[self.user1, self.user2])
132+
133+
self.assertEqual(Notification.objects.all().count(), 2)
134+
self.assertEqual(len(MockDispatcher.dispatched_notifications), 2)
127135

128136
for user in users:
129137
notification = user.notifications.get()
@@ -134,23 +142,36 @@ def test_handler_should_create_notification_using_template_data(self):
134142
self.assertEqual(notification.get_extra_data(), {'some_value': 123})
135143
self.assertEqual(related_object.name, 'first_recipient')
136144
self.assertEqual(related_object.content_object, self.user1)
137-
if user.username == 'Jack':
138-
self.assertIn(notification, MockDispatcher.dispatched_notifications)
139-
140-
self.assertEqual(len(MockDispatcher.dispatched_notifications), 1)
145+
self.assertIn(notification, MockDispatcher.dispatched_notifications)
141146

142147
# Repeated notification should use the same template
143148
test_signal_data.send(sender=None, recipients=[self.user1])
144149
notifications = self.user1.notifications.all()
145150
self.assertEqual(notifications[0].template, notifications[1].template)
146151

147-
# Test _can_handle() method is used
148-
self.user1.notifications.all().delete()
149-
test_signal_data.send(sender=None, recipients=[self.user1], can_handle=False)
150-
self.assertEqual(self.user1.notifications.count(), 0)
151-
152152
def test_handler_should_create_notification_using_template_slug(self):
153153
test_signal_slug.send(sender=MockSender, recipients=[self.user1])
154154
notification = self.user1.notifications.get()
155155
self.assertEqual(notification.template.admin_template, self.template)
156156
self.assertEqual(notification.title, 'Hello slug!')
157+
158+
def test_can_handle_method_should_be_used(self):
159+
test_signal_data.send(sender=None, recipients=[self.user1], can_handle=False)
160+
self.assertEqual(self.user1.notifications.count(), 0)
161+
self.assertEqual(len(MockDispatcher.dispatched_notifications), 0)
162+
163+
def test_can_create_notification_method_should_be_used(self):
164+
test_signal_data.send(sender=None, recipients=[self.user1], can_create=False)
165+
self.assertEqual(self.user1.notifications.count(), 0)
166+
self.assertEqual(len(MockDispatcher.dispatched_notifications), 0)
167+
168+
def test_can_save_notification_method_should_be_used(self):
169+
test_signal_data.send(sender=None, recipients=[self.user1], can_save=False)
170+
self.assertEqual(self.user1.notifications.count(), 0)
171+
self.assertEqual(len(MockDispatcher.dispatched_notifications), 1)
172+
self.assertIsNone(MockDispatcher.dispatched_notifications[0].pk)
173+
174+
def test_can_dispatch_notification_method_should_be_used(self):
175+
test_signal_data.send(sender=None, recipients=[self.user1], can_dispatch=False)
176+
self.assertEqual(self.user1.notifications.count(), 1)
177+
self.assertEqual(len(MockDispatcher.dispatched_notifications), 0)

example/tests/test_models.py

Lines changed: 70 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -103,26 +103,28 @@ def setUp(self):
103103
self.author = User.objects.create_user('John')
104104
self.article = Article.objects.create(title='The Old Witch', author=self.author)
105105
self.random_user = User.objects.create_user('Mr.Random')
106+
self.random_user2 = User.objects.create_user('Mr.Random 2')
106107

107108
self.template = NotificationTemplate.objects.create(
108109
title='{{article}}',
109110
text='{{author}} created a new article named {{article}}.',
110111
trigger_action='{{article.get_absolute_url}}',
111112
)
112113

113-
self.notification = Notification.objects.create(
114+
self.notification = Notification(
114115
recipient=self.recipient,
115116
template=self.template,
116-
related_objects={
117-
'article': self.article,
118-
'author': self.article.author,
119-
'random_user': self.random_user,
120-
},
121-
extra_data={
122-
'some_value': 123,
123-
'decimal_value': Decimal('1.55'),
124-
}
125117
)
118+
self.notification.set_local_related_objects({
119+
'article': self.article,
120+
'author': self.article.author,
121+
'random_user': self.random_user,
122+
})
123+
self.notification.set_extra_data({
124+
'some_value': 123,
125+
'decimal_value': Decimal('1.55'),
126+
})
127+
self.notification.save()
126128

127129
def test_generated_fields_should_use_template_for_rendering(self):
128130
self.assertEqual(self.notification.title, 'The Old Witch')
@@ -148,11 +150,27 @@ def test_extra_data_should_be_dictionary(self):
148150
self.notification.set_extra_data(1000)
149151

150152
def test_related_objects_and_extra_data_should_not_contain_same_keys(self):
153+
self.notification.set_local_related_objects({'random_user': self.random_user})
154+
with self.assertRaises(ValueError):
155+
self.notification.context
156+
with self.assertRaises(ValueError):
157+
self.notification.save()
158+
159+
self.notification.set_local_related_objects({'some_value': self.random_user})
160+
with self.assertRaises(ValueError):
161+
self.notification.context
162+
with self.assertRaises(ValueError):
163+
self.notification.save()
164+
165+
self.notification.set_local_related_objects({})
151166
self.notification.set_extra_data({'article': 123})
167+
with self.assertRaises(ValueError):
168+
self.notification.context
152169
with self.assertRaises(ValueError):
153170
self.notification.save()
154171

155172
def test_context_should_contain_related_objects_as_proxies_and_extra_data(self):
173+
self.notification.set_local_related_objects({'local_user': self.random_user2})
156174
ctx = self.notification.context
157175

158176
self.assertTrue(isinstance(ctx['article'], SecureRelatedObject))
@@ -164,6 +182,9 @@ def test_context_should_contain_related_objects_as_proxies_and_extra_data(self):
164182
self.assertTrue(isinstance(ctx['random_user'], SecureRelatedObject))
165183
self.assertEqual(ctx['random_user']._object, self.random_user)
166184

185+
self.assertTrue(isinstance(ctx['local_user'], SecureRelatedObject))
186+
self.assertEqual(ctx['local_user']._object, self.random_user2)
187+
167188
self.assertEqual(ctx['some_value'], 123)
168189
self.assertEqual(ctx['decimal_value'], '1.55')
169190

@@ -186,26 +207,55 @@ def test_creating_notification_should_not_be_possible_with_related_objects_in_in
186207
['abc', 'abc'],
187208
{'abc': 'abc'},
188209
)
210+
notification = Notification.objects.create(recipient=self.recipient, template=self.template)
189211
for related_objects in INVALID_RELATED_OBJECTS:
190212
with self.assertRaises(TypeError):
191-
Notification.objects.create(
192-
recipient=self.recipient,
193-
template=self.template,
194-
related_objects=related_objects
195-
)
213+
notification.set_local_related_objects(related_objects)
214+
notification.save()
215+
notification.set_local_related_objects({})
196216

197217
def test_creating_notification_should_allow_list_of_related_objects(self):
198-
notification = Notification.objects.create(
199-
recipient=self.recipient,
200-
template=self.template,
201-
related_objects=[self.random_user],
202-
)
218+
notification = Notification(recipient=self.recipient, template=self.template)
219+
notification.set_local_related_objects([self.random_user])
220+
notification.save()
203221

204222
self.assertEqual(notification.related_objects.count(), 1)
205223
related_object = notification.related_objects.get()
206224
self.assertEqual(related_object.name, None)
207225
self.assertEqual(related_object.content_object, self.random_user)
208226
self.assertEqual(notification.context, {})
209227

228+
def test_saving_notification_should_save_list_of_local_related_objects_into_db(self):
229+
notification = Notification(recipient=self.recipient, template=self.template)
230+
231+
notification.set_local_related_objects([self.random_user2])
232+
self.assertEqual(len(notification._local_related_objects_list), 1)
233+
self.assertEqual(notification._local_related_objects_list[0], self.random_user2)
234+
self.assertEqual(notification.related_objects.count(), 0)
235+
236+
notification.save()
237+
self.assertEqual(len(notification._local_related_objects_list), 0)
238+
self.assertEqual(notification.related_objects.count(), 1)
239+
240+
related_object = notification.related_objects.get()
241+
self.assertEqual(related_object.name, None)
242+
self.assertEqual(related_object.content_object, self.random_user2)
243+
244+
def test_saving_notification_should_save_dictionary_of_local_related_objects_into_db(self):
245+
notification = Notification(recipient=self.recipient, template=self.template)
246+
247+
notification.set_local_related_objects({'random_user2': self.random_user2})
248+
self.assertEqual(len(notification._local_related_objects_dict), 1)
249+
self.assertEqual(notification._local_related_objects_dict['random_user2'], self.random_user2)
250+
self.assertEqual(notification.related_objects.count(), 0)
251+
252+
notification.save()
253+
self.assertEqual(len(notification._local_related_objects_dict), 0)
254+
self.assertEqual(notification.related_objects.count(), 1)
255+
256+
related_object = notification.related_objects.get()
257+
self.assertEqual(related_object.name, 'random_user2')
258+
self.assertEqual(related_object.content_object, self.random_user2)
259+
210260
def test_notification_should_have_string_representation(self):
211261
self.assertEqual(str(self.notification), 'notification #{}'.format(self.notification.pk))

pynotify/handlers.py

Lines changed: 52 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ class BaseHandler(metaclass=HandlerMeta):
6161

6262
@cached_property
6363
def _template(self):
64+
"""
65+
Returns notification template that will be used for creation of notification(s).
66+
"""
6467
template_slug = self.get_template_slug()
6568
if template_slug:
6669
admin_template = AdminNotificationTemplate.objects.get(slug=template_slug)
@@ -75,50 +78,62 @@ def _template(self):
7578

7679
return template
7780

81+
def _create_notification(self, recipient):
82+
"""
83+
Creates notification for ``recipient``.
84+
"""
85+
notification = Notification(recipient=recipient, template=self._template)
86+
87+
extra_data = self.get_extra_data()
88+
if extra_data:
89+
notification.set_extra_data(extra_data)
90+
91+
related_objects = self.get_related_objects()
92+
if related_objects:
93+
notification.set_local_related_objects(related_objects)
94+
95+
if self._can_save_notification(notification):
96+
notification.save()
97+
98+
return notification
99+
78100
def _init_dispatchers(self):
79-
self.dispatchers = []
101+
"""
102+
Initializes dipatchers that will be used for sending of notification(s).
103+
"""
104+
self._dispatchers = []
80105
dispatcher_classes = self.get_dispatcher_classes()
81106
if dispatcher_classes:
82107
for dispatcher_class in dispatcher_classes:
83-
self.dispatchers.append(self._init_dispatcher(dispatcher_class))
108+
self._dispatchers.append(self._init_dispatcher(dispatcher_class))
84109

85110
def _init_dispatcher(self, dispatcher_class):
86-
return dispatcher_class()
87-
88-
def _can_create_notification(self, recipient):
89111
"""
90-
Returns ``True`` if notification can be created for ``recipient``.
112+
Initializes a single dispatcher. Override this method if you need specific initialization procedure.
91113
"""
92-
return True
114+
return dispatcher_class()
93115

94-
def _create_notification(self, recipient):
116+
def _can_handle(self):
95117
"""
96-
Creates notification for ``recipient``.
118+
Returns ``True`` if handler can handle creating of notification(s).
97119
"""
98-
if self._can_create_notification(recipient):
99-
return Notification.objects.create(
100-
recipient=recipient,
101-
template=self._template,
102-
related_objects=self.get_related_objects(),
103-
extra_data=self.get_extra_data(),
104-
)
120+
return True
105121

106-
def _can_dispatch_notification(self, notification, dispatcher):
122+
def _can_create_notification(self, recipient):
107123
"""
108-
Returns ``True`` if ``notification`` can be dispatched using ``dispatcher``.
124+
Returns ``True`` if notification can be created for ``recipient``.
109125
"""
110126
return True
111127

112-
def _dispatch_notification(self, notification, dispatcher):
128+
def _can_save_notification(self, notification):
113129
"""
114-
Dispatches ``notification`` using ``dispatcher``.
130+
Returns ``True`` if ``notification`` can be saved into DB.
115131
"""
116-
if self._can_dispatch_notification(notification, dispatcher):
117-
dispatcher.dispatch(notification)
132+
return True
118133

119-
def _can_handle(self):
134+
def _can_dispatch_notification(self, notification, dispatcher):
120135
"""
121-
Returns ``True`` if handler should handle creating of notification(s).
136+
Returns ``True`` if ``notification`` can be dispatched using ``dispatcher``.
122137
"""
123138
return True
124139

@@ -127,14 +142,19 @@ def handle(self, signal_kwargs):
127142
Handles creation of notifications from ``signal_kwargs``.
128143
"""
129144
self.signal_kwargs = signal_kwargs
130-
if self._can_handle():
131-
self._init_dispatchers()
132-
for recipient in self.get_recipients():
133-
notification = self._create_notification(recipient)
134-
135-
if notification:
136-
for dispatcher in self.dispatchers:
137-
self._dispatch_notification(notification, dispatcher)
145+
146+
if not self._can_handle():
147+
return
148+
149+
self._init_dispatchers()
150+
for recipient in self.get_recipients():
151+
if not self._can_create_notification(recipient):
152+
continue
153+
154+
notification = self._create_notification(recipient)
155+
for dispatcher in self._dispatchers:
156+
if self._can_dispatch_notification(notification, dispatcher):
157+
dispatcher.dispatch(notification)
138158

139159
def get_recipients(self):
140160
"""

0 commit comments

Comments
 (0)