31
31
from django.core.mail import send_mail
33
QUEUE_ALL = getattr(settings, "NOTIFICATION_QUEUE_ALL", False)
33
QUEUE_ALL = getattr(settings, 'NOTIFICATION_QUEUE_ALL', False)
35
36
class LanguageStoreNotAvailable(Exception):
38
40
class NoticeType(models.Model):
40
42
label = models.CharField(_('label'), max_length=40)
41
43
display = models.CharField(_('display'), max_length=50)
42
44
description = models.CharField(_('description'), max_length=100)
44
# by default only on for media with sensitivity less than or equal to this number
46
# by default only on for media with sensitivity less than or equal to this
45
48
default = models.IntegerField(_('default'))
47
50
def __unicode__(self):
51
verbose_name = _("notice type")
52
verbose_name_plural = _("notice types")
54
verbose_name = _('notice type')
55
verbose_name_plural = _('notice types')
55
58
# if this gets updated, the create() method below needs to be as well...
60
63
# how spam-sensitive is the medium
61
64
NOTICE_MEDIA_DEFAULTS = {
65
69
class NoticeSetting(models.Model):
67
Indicates, for a given user, whether to send notifications
68
of a given type to a given medium.
70
"""Indicates, for a given user, whether to send notifications of a given
71
type to a given medium."""
71
73
user = models.ForeignKey(User, verbose_name=_('user'))
72
74
notice_type = models.ForeignKey(NoticeType, verbose_name=_('notice type'))
74
76
send = models.BooleanField(_('send'))
77
verbose_name = _("notice setting")
78
verbose_name_plural = _("notice settings")
79
unique_together = ("user", "notice_type", "medium")
79
verbose_name = _('notice setting')
80
verbose_name_plural = _('notice settings')
81
unique_together = ('user', 'notice_type', 'medium')
81
84
def get_notification_setting(user, notice_type, medium):
83
86
return NoticeSetting.objects.get(user=user, notice_type=notice_type, medium=medium)
84
87
except NoticeSetting.DoesNotExist:
85
88
default = (NOTICE_MEDIA_DEFAULTS[medium] <= notice_type.default)
86
setting = NoticeSetting(user=user, notice_type=notice_type, medium=medium, send=default)
89
setting = NoticeSetting(
90
user=user, notice_type=notice_type, medium=medium, send=default)
90
95
def should_send(user, notice_type, medium):
91
96
return get_notification_setting(user, notice_type, medium).send
94
99
class NoticeManager(models.Manager):
96
101
def notices_for(self, user, archived=False, unseen=None, on_site=None):
98
returns Notice objects for the given user.
102
"""returns Notice objects for the given user.
100
104
If archived=False, it only include notices not archived.
101
105
If archived=True, it returns all notices for that user.
117
122
def unseen_count_for(self, user, **kwargs):
119
returns the number of unseen notices for the given user but does not
123
"""returns the number of unseen notices for the given user but does not
122
125
return self.notices_for(user, unseen=True, **kwargs).count()
124
128
class Notice(models.Model):
126
130
user = models.ForeignKey(User, verbose_name=_('user'))
157
ordering = ["-added"]
158
verbose_name = _("notice")
159
verbose_name_plural = _("notices")
161
ordering = ['-added']
162
verbose_name = _('notice')
163
verbose_name_plural = _('notices')
161
165
def get_absolute_url(self):
162
return ("notification_notice", [str(self.pk)])
166
return ('notification_notice', [str(self.pk)])
163
167
get_absolute_url = models.permalink(get_absolute_url)
165
170
class NoticeQueueBatch(models.Model):
168
173
Denormalized data for a notice.
170
176
pickled_data = models.TextField()
172
179
def create_notice_type(label, display, description, default=2, verbosity=1):
174
Creates a new NoticeType.
176
This is intended to be used by other apps as a post_syncdb manangement step.
180
"""Creates a new NoticeType.
182
This is intended to be used by other apps as a post_syncdb
179
187
notice_type = NoticeType.objects.get(label=label)
191
199
notice_type.save()
192
200
if verbosity > 1:
193
print "Updated %s NoticeType" % label
201
print 'Updated %s NoticeType' % label
194
202
except NoticeType.DoesNotExist:
195
NoticeType(label=label, display=display, description=description, default=default).save()
203
NoticeType(label=label, display=display,
204
description=description, default=default).save()
196
205
if verbosity > 1:
197
print "Created %s NoticeType" % label
206
print 'Created %s NoticeType' % label
199
209
def get_notification_language(user):
205
215
if getattr(settings, 'NOTIFICATION_LANGUAGE_MODULE', False):
207
app_label, model_name = settings.NOTIFICATION_LANGUAGE_MODULE.split('.')
217
app_label, model_name = settings.NOTIFICATION_LANGUAGE_MODULE.split(
208
219
model = models.get_model(app_label, model_name)
209
language_model = model._default_manager.get(user__id__exact=user.id)
220
language_model = model._default_manager.get(
221
user__id__exact=user.id)
210
222
if hasattr(language_model, 'language'):
211
223
return language_model.language
212
224
except (ImportError, ImproperlyConfigured, model.DoesNotExist):
213
225
raise LanguageStoreNotAvailable
214
226
raise LanguageStoreNotAvailable
216
229
def get_formatted_messages(formats, label, context):
218
Returns a dictionary with the format identifier as the key. The values are
219
are fully rendered templates with the given context.
230
"""Returns a dictionary with the format identifier as the key.
232
The values are are fully rendered templates with the given context.
221
235
format_templates = {}
222
236
for format in formats:
223
237
# conditionally turn off autoescaping for .txt extensions in format
224
if format.endswith(".txt"):
238
if format.endswith('.txt'):
225
239
context.autoescape = False
227
241
context.autoescape = True
244
258
You can pass in on_site=False to prevent the notice emitted from being
245
259
displayed on the site.
247
262
if extra_context is None:
248
263
extra_context = {}
250
265
notice_type = NoticeType.objects.get(label=label)
252
267
current_site = Site.objects.get_current()
253
268
notices_url = u"http://%s%s" % (
254
269
unicode(current_site),
255
reverse("notification_notices"),
270
reverse('notification_notices'),
258
273
current_language = get_language()
280
295
# update context with user specific translations
281
296
context = Context({
283
"notice": ugettext(notice_type.display),
284
"notices_url": notices_url,
285
"current_site": current_site,
298
'notice': ugettext(notice_type.display),
299
'notices_url': notices_url,
300
'current_site': current_site,
287
302
context.update(extra_context)
301
316
notice = Notice.objects.create(user=user, message=messages['notice.html'],
302
notice_type=notice_type, on_site=on_site)
303
if should_send(user, notice_type, "1") and user.email: # Email
317
notice_type=notice_type, on_site=on_site)
318
if should_send(user, notice_type, '1') and user.email: # Email
304
319
recipients.append(user.email)
305
320
send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, recipients)
307
322
# reset environment to original language
308
323
activate(current_language)
310
326
def send(*args, **kwargs):
312
A basic interface around both queue and send_now. This honors a global
313
flag NOTIFICATION_QUEUE_ALL that helps determine whether all calls should
314
be queued or not. A per call ``queue`` or ``now`` keyword argument can be
315
used to always override the default global behavior.
317
queue_flag = kwargs.pop("queue", False)
318
now_flag = kwargs.pop("now", False)
319
assert not (queue_flag and now_flag), "'queue' and 'now' cannot both be True."
327
"""A basic interface around both queue and send_now.
329
This honors a global flag NOTIFICATION_QUEUE_ALL that helps
330
determine whether all calls should be queued or not. A per call
331
``queue`` or ``now`` keyword argument can be used to always override
332
the default global behavior.
335
queue_flag = kwargs.pop('queue', False)
336
now_flag = kwargs.pop('now', False)
338
queue_flag and now_flag), "'queue' and 'now' cannot both be True."
321
340
return queue(*args, **kwargs)
326
345
return queue(*args, **kwargs)
328
347
return send_now(*args, **kwargs)
330
350
def queue(users, label, extra_context=None, on_site=True):
332
Queue the notification in NoticeQueueBatch. This allows for large amounts
333
of user notifications to be deferred to a seperate process running outside
351
"""Queue the notification in NoticeQueueBatch.
353
This allows for large amounts of user notifications to be deferred
354
to a seperate process running outside the webserver.
336
357
if extra_context is None:
337
358
extra_context = {}
338
359
if isinstance(users, QuerySet):
339
users = [row["pk"] for row in users.values("pk")]
360
users = [row['pk'] for row in users.values('pk')]
341
362
users = [user.pk for user in users]
343
364
for user in users:
344
365
notices.append((user, label, extra_context, on_site))
345
NoticeQueueBatch(pickled_data=pickle.dumps(notices).encode("base64")).save()
366
NoticeQueueBatch(pickled_data=pickle.dumps(
367
notices).encode('base64')).save()
347
370
class ObservedItemManager(models.Manager):
349
372
def all_for(self, observed, signal):
351
Returns all ObservedItems for an observed object,
352
to be sent when a signal is emited.
373
"""Returns all ObservedItems for an observed object, to be sent when a
354
375
content_type = ContentType.objects.get_for_model(observed)
355
observed_items = self.filter(content_type=content_type, object_id=observed.id, signal=signal)
376
observed_items = self.filter(
377
content_type=content_type, object_id=observed.id, signal=signal)
356
378
return observed_items
358
380
def get_for(self, observed, observer, signal):
359
381
content_type = ContentType.objects.get_for_model(observed)
360
observed_item = self.get(content_type=content_type, object_id=observed.id, user=observer, signal=signal)
382
observed_item = self.get(
383
content_type=content_type, object_id=observed.id, user=observer, signal=signal)
361
384
return observed_item
391
414
def observe(observed, observer, notice_type_label, signal='post_save'):
393
Create a new ObservedItem.
395
To be used by applications to register a user as an observer for some object.
415
"""Create a new ObservedItem.
417
To be used by applications to register a user as an observer for
397
421
notice_type = NoticeType.objects.get(label=notice_type_label)
398
422
observed_item = ObservedItem(user=observer, observed_object=observed,
400
424
observed_item.save()
401
425
return observed_item
403
428
def stop_observing(observed, observer, signal='post_save'):
405
Remove an observed item.
429
"""Remove an observed item."""
407
430
observed_item = ObservedItem.objects.get_for(observed, observer, signal)
408
431
observed_item.delete()
410
434
def send_observation_notices_for(observed, signal='post_save'):
412
Send a notice for each registered user about an observed object.
435
"""Send a notice for each registered user about an observed object."""
414
436
observed_items = ObservedItem.objects.all_for(observed, signal)
415
437
for observed_item in observed_items:
416
438
observed_item.send_notice()
417
439
return observed_items
419
442
def is_observing(observed, observer, signal='post_save'):
420
443
if isinstance(observer, AnonymousUser):
423
observed_items = ObservedItem.objects.get_for(observed, observer, signal)
446
observed_items = ObservedItem.objects.get_for(
447
observed, observer, signal)
425
449
except ObservedItem.DoesNotExist:
427
451
except ObservedItem.MultipleObjectsReturned:
430
455
def handle_observations(sender, instance, *args, **kw):
431
456
send_observation_notices_for(instance)