mirror of
https://github.com/Hopiu/django-notifications.git
synced 2026-03-16 21:30:24 +00:00
parent
c271193215
commit
d55aae4e3d
7 changed files with 367 additions and 309 deletions
21
README.rst
21
README.rst
|
|
@ -401,6 +401,27 @@ In this example the target object can be of type Foo or Bar and the appropriate
|
|||
|
||||
Thanks to @DaWy
|
||||
|
||||
``AbstractNotification`` model
|
||||
------------------------------
|
||||
|
||||
In case you need to customize the notification model in order to add field or
|
||||
customised features that depend on your application, you can inherit and extend
|
||||
the ``AbstractNotification`` model, example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.db import models
|
||||
from notifications.base.models import AbstractNotification
|
||||
|
||||
|
||||
class Notification(AbstractNotification):
|
||||
# custom field example
|
||||
category = models.ForeignKey('myapp.Category',
|
||||
on_delete=models.CASCADE)
|
||||
|
||||
class Meta(AbstractNotification.Meta):
|
||||
abstract = False
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
|
|
|
|||
0
notifications/base/__init__.py
Normal file
0
notifications/base/__init__.py
Normal file
317
notifications/base/models.py
Normal file
317
notifications/base/models.py
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=too-many-lines
|
||||
from distutils.version import \
|
||||
StrictVersion # pylint: disable=no-name-in-module,import-error
|
||||
|
||||
from django import get_version
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import models
|
||||
from django.db.models.query import QuerySet
|
||||
from django.utils import timezone
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.six import text_type
|
||||
from jsonfield.fields import JSONField
|
||||
from model_utils import Choices
|
||||
|
||||
from notifications import settings as notifications_settings
|
||||
from notifications.signals import notify
|
||||
from notifications.utils import id2slug
|
||||
from swapper import load_model
|
||||
|
||||
if StrictVersion(get_version()) >= StrictVersion('1.8.0'):
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey # noqa
|
||||
else:
|
||||
from django.contrib.contenttypes.generic import GenericForeignKey # noqa
|
||||
|
||||
|
||||
EXTRA_DATA = notifications_settings.get_config()['USE_JSONFIELD']
|
||||
|
||||
|
||||
def is_soft_delete():
|
||||
return notifications_settings.get_config()['SOFT_DELETE']
|
||||
|
||||
|
||||
def assert_soft_delete():
|
||||
if not is_soft_delete():
|
||||
# msg = """To use 'deleted' field, please set 'SOFT_DELETE'=True in settings.
|
||||
# Otherwise NotificationQuerySet.unread and NotificationQuerySet.read do NOT filter by 'deleted' field.
|
||||
# """
|
||||
msg = 'REVERTME'
|
||||
raise ImproperlyConfigured(msg)
|
||||
|
||||
|
||||
class NotificationQuerySet(models.query.QuerySet):
|
||||
''' Notification QuerySet '''
|
||||
def unsent(self):
|
||||
return self.filter(emailed=False)
|
||||
|
||||
def sent(self):
|
||||
return self.filter(emailed=True)
|
||||
|
||||
def unread(self, include_deleted=False):
|
||||
"""Return only unread items in the current queryset"""
|
||||
if is_soft_delete() and not include_deleted:
|
||||
return self.filter(unread=True, deleted=False)
|
||||
|
||||
# When SOFT_DELETE=False, developers are supposed NOT to touch 'deleted' field.
|
||||
# In this case, to improve query performance, don't filter by 'deleted' field
|
||||
return self.filter(unread=True)
|
||||
|
||||
def read(self, include_deleted=False):
|
||||
"""Return only read items in the current queryset"""
|
||||
if is_soft_delete() and not include_deleted:
|
||||
return self.filter(unread=False, deleted=False)
|
||||
|
||||
# When SOFT_DELETE=False, developers are supposed NOT to touch 'deleted' field.
|
||||
# In this case, to improve query performance, don't filter by 'deleted' field
|
||||
return self.filter(unread=False)
|
||||
|
||||
def mark_all_as_read(self, recipient=None):
|
||||
"""Mark as read any unread messages in the current queryset.
|
||||
|
||||
Optionally, filter these by recipient first.
|
||||
"""
|
||||
# We want to filter out read ones, as later we will store
|
||||
# the time they were marked as read.
|
||||
qset = self.unread(True)
|
||||
if recipient:
|
||||
qset = qset.filter(recipient=recipient)
|
||||
|
||||
return qset.update(unread=False)
|
||||
|
||||
def mark_all_as_unread(self, recipient=None):
|
||||
"""Mark as unread any read messages in the current queryset.
|
||||
|
||||
Optionally, filter these by recipient first.
|
||||
"""
|
||||
qset = self.read(True)
|
||||
|
||||
if recipient:
|
||||
qset = qset.filter(recipient=recipient)
|
||||
|
||||
return qset.update(unread=True)
|
||||
|
||||
def deleted(self):
|
||||
"""Return only deleted items in the current queryset"""
|
||||
assert_soft_delete()
|
||||
return self.filter(deleted=True)
|
||||
|
||||
def active(self):
|
||||
"""Return only active(un-deleted) items in the current queryset"""
|
||||
assert_soft_delete()
|
||||
return self.filter(deleted=False)
|
||||
|
||||
def mark_all_as_deleted(self, recipient=None):
|
||||
"""Mark current queryset as deleted.
|
||||
Optionally, filter by recipient first.
|
||||
"""
|
||||
assert_soft_delete()
|
||||
qset = self.active()
|
||||
if recipient:
|
||||
qset = qset.filter(recipient=recipient)
|
||||
|
||||
return qset.update(deleted=True)
|
||||
|
||||
def mark_all_as_active(self, recipient=None):
|
||||
"""Mark current queryset as active(un-deleted).
|
||||
Optionally, filter by recipient first.
|
||||
"""
|
||||
assert_soft_delete()
|
||||
qset = self.deleted()
|
||||
if recipient:
|
||||
qset = qset.filter(recipient=recipient)
|
||||
|
||||
return qset.update(deleted=False)
|
||||
|
||||
def mark_as_unsent(self, recipient=None):
|
||||
qset = self.sent()
|
||||
if recipient:
|
||||
qset = qset.filter(recipient=recipient)
|
||||
return qset.update(emailed=False)
|
||||
|
||||
def mark_as_sent(self, recipient=None):
|
||||
qset = self.unsent()
|
||||
if recipient:
|
||||
qset = qset.filter(recipient=recipient)
|
||||
return qset.update(emailed=True)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class AbstractNotification(models.Model):
|
||||
"""
|
||||
Action model describing the actor acting out a verb (on an optional
|
||||
target).
|
||||
Nomenclature based on http://activitystrea.ms/specs/atom/1.0/
|
||||
|
||||
Generalized Format::
|
||||
|
||||
<actor> <verb> <time>
|
||||
<actor> <verb> <target> <time>
|
||||
<actor> <verb> <action_object> <target> <time>
|
||||
|
||||
Examples::
|
||||
|
||||
<justquick> <reached level 60> <1 minute ago>
|
||||
<brosner> <commented on> <pinax/pinax> <2 hours ago>
|
||||
<washingtontimes> <started follow> <justquick> <8 minutes ago>
|
||||
<mitsuhiko> <closed> <issue 70> on <mitsuhiko/flask> <about 2 hours ago>
|
||||
|
||||
Unicode Representation::
|
||||
|
||||
justquick reached level 60 1 minute ago
|
||||
mitsuhiko closed issue 70 on mitsuhiko/flask 3 hours ago
|
||||
|
||||
HTML Representation::
|
||||
|
||||
<a href="http://oebfare.com/">brosner</a> commented on <a href="http://github.com/pinax/pinax">pinax/pinax</a> 2 hours ago # noqa
|
||||
|
||||
"""
|
||||
LEVELS = Choices('success', 'info', 'warning', 'error')
|
||||
level = models.CharField(choices=LEVELS, default=LEVELS.info, max_length=20)
|
||||
|
||||
recipient = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
blank=False,
|
||||
related_name='notifications',
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
unread = models.BooleanField(default=True, blank=False, db_index=True)
|
||||
|
||||
actor_content_type = models.ForeignKey(ContentType, related_name='notify_actor', on_delete=models.CASCADE)
|
||||
actor_object_id = models.CharField(max_length=255)
|
||||
actor = GenericForeignKey('actor_content_type', 'actor_object_id')
|
||||
|
||||
verb = models.CharField(max_length=255)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
|
||||
target_content_type = models.ForeignKey(
|
||||
ContentType,
|
||||
related_name='notify_target',
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
target_object_id = models.CharField(max_length=255, blank=True, null=True)
|
||||
target = GenericForeignKey('target_content_type', 'target_object_id')
|
||||
|
||||
action_object_content_type = models.ForeignKey(ContentType, blank=True, null=True,
|
||||
related_name='notify_action_object', on_delete=models.CASCADE)
|
||||
action_object_object_id = models.CharField(max_length=255, blank=True, null=True)
|
||||
action_object = GenericForeignKey('action_object_content_type', 'action_object_object_id')
|
||||
|
||||
timestamp = models.DateTimeField(default=timezone.now, db_index=True)
|
||||
|
||||
public = models.BooleanField(default=True, db_index=True)
|
||||
deleted = models.BooleanField(default=False, db_index=True)
|
||||
emailed = models.BooleanField(default=False, db_index=True)
|
||||
|
||||
data = JSONField(blank=True, null=True)
|
||||
objects = NotificationQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
ordering = ('-timestamp',)
|
||||
app_label = 'notifications'
|
||||
# speed up notifications count query
|
||||
index_together = ('recipient', 'unread')
|
||||
|
||||
def __str__(self):
|
||||
ctx = {
|
||||
'actor': self.actor,
|
||||
'verb': self.verb,
|
||||
'action_object': self.action_object,
|
||||
'target': self.target,
|
||||
'timesince': self.timesince()
|
||||
}
|
||||
if self.target:
|
||||
if self.action_object:
|
||||
return u'%(actor)s %(verb)s %(action_object)s on %(target)s %(timesince)s ago' % ctx
|
||||
return u'%(actor)s %(verb)s %(target)s %(timesince)s ago' % ctx
|
||||
if self.action_object:
|
||||
return u'%(actor)s %(verb)s %(action_object)s %(timesince)s ago' % ctx
|
||||
return u'%(actor)s %(verb)s %(timesince)s ago' % ctx
|
||||
|
||||
def timesince(self, now=None):
|
||||
"""
|
||||
Shortcut for the ``django.utils.timesince.timesince`` function of the
|
||||
current timestamp.
|
||||
"""
|
||||
from django.utils.timesince import timesince as timesince_
|
||||
return timesince_(self.timestamp, now)
|
||||
|
||||
@property
|
||||
def slug(self):
|
||||
return id2slug(self.id)
|
||||
|
||||
def mark_as_read(self):
|
||||
if self.unread:
|
||||
self.unread = False
|
||||
self.save()
|
||||
|
||||
def mark_as_unread(self):
|
||||
if not self.unread:
|
||||
self.unread = True
|
||||
self.save()
|
||||
|
||||
|
||||
def notify_handler(verb, **kwargs):
|
||||
"""
|
||||
Handler function to create Notification instance upon action signal call.
|
||||
"""
|
||||
# Pull the options out of kwargs
|
||||
kwargs.pop('signal', None)
|
||||
recipient = kwargs.pop('recipient')
|
||||
actor = kwargs.pop('sender')
|
||||
optional_objs = [
|
||||
(kwargs.pop(opt, None), opt)
|
||||
for opt in ('target', 'action_object')
|
||||
]
|
||||
public = bool(kwargs.pop('public', True))
|
||||
description = kwargs.pop('description', None)
|
||||
timestamp = kwargs.pop('timestamp', timezone.now())
|
||||
Notification = load_model('notifications', 'Notification')
|
||||
level = kwargs.pop('level', Notification.LEVELS.info)
|
||||
|
||||
# Check if User or Group
|
||||
if isinstance(recipient, Group):
|
||||
recipients = recipient.user_set.all()
|
||||
elif isinstance(recipient, (QuerySet, list)):
|
||||
recipients = recipient
|
||||
else:
|
||||
recipients = [recipient]
|
||||
|
||||
new_notifications = []
|
||||
|
||||
for recipient in recipients:
|
||||
newnotify = Notification(
|
||||
recipient=recipient,
|
||||
actor_content_type=ContentType.objects.get_for_model(actor),
|
||||
actor_object_id=actor.pk,
|
||||
verb=text_type(verb),
|
||||
public=public,
|
||||
description=description,
|
||||
timestamp=timestamp,
|
||||
level=level,
|
||||
)
|
||||
|
||||
# Set optional objects
|
||||
for obj, opt in optional_objs:
|
||||
if obj is not None:
|
||||
setattr(newnotify, '%s_object_id' % opt, obj.pk)
|
||||
setattr(newnotify, '%s_content_type' % opt,
|
||||
ContentType.objects.get_for_model(obj))
|
||||
|
||||
if kwargs and EXTRA_DATA:
|
||||
newnotify.data = kwargs
|
||||
|
||||
newnotify.save()
|
||||
new_notifications.append(newnotify)
|
||||
|
||||
return new_notifications
|
||||
|
||||
|
||||
# connect the signal
|
||||
notify.connect(notify_handler, dispatch_uid='notifications.models.notification')
|
||||
|
|
@ -4,6 +4,7 @@ from __future__ import unicode_literals
|
|||
from django.db import models, migrations
|
||||
import django.utils.timezone
|
||||
from django.conf import settings
|
||||
import swapper
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
|
@ -33,6 +34,7 @@ class Migration(migrations.Migration):
|
|||
('target_content_type', models.ForeignKey(related_name='notify_target', blank=True, to='contenttypes.ContentType', null=True, on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'swappable': swapper.swappable_setting('notifications', 'Notification'),
|
||||
'ordering': ('-timestamp',),
|
||||
},
|
||||
bases=(models.Model,),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 2.1.8 on 2019-04-27 21:26
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('notifications', '0007_add_timestamp_index'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterIndexTogether(
|
||||
name='notification',
|
||||
index_together={('recipient', 'unread')},
|
||||
),
|
||||
]
|
||||
|
|
@ -1,312 +1,10 @@
|
|||
''' Django notifications models file '''
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=too-many-lines
|
||||
from distutils.version import StrictVersion # pylint: disable=no-name-in-module,import-error
|
||||
from swapper import swappable_setting
|
||||
|
||||
from django import get_version
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import models
|
||||
from django.db.models.query import QuerySet
|
||||
from django.utils import timezone
|
||||
from django.utils.six import text_type
|
||||
from jsonfield.fields import JSONField
|
||||
from model_utils import Choices
|
||||
from notifications import settings as notifications_settings
|
||||
from notifications.signals import notify
|
||||
from notifications.utils import id2slug
|
||||
from .base.models import AbstractNotification, notify_handler # noqa
|
||||
|
||||
if StrictVersion(get_version()) >= StrictVersion('1.8.0'):
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey # noqa
|
||||
else:
|
||||
from django.contrib.contenttypes.generic import GenericForeignKey # noqa
|
||||
|
||||
class Notification(AbstractNotification):
|
||||
|
||||
EXTRA_DATA = notifications_settings.get_config()['USE_JSONFIELD']
|
||||
|
||||
|
||||
def is_soft_delete():
|
||||
return notifications_settings.get_config()['SOFT_DELETE']
|
||||
|
||||
|
||||
def assert_soft_delete():
|
||||
if not is_soft_delete():
|
||||
msg = """To use 'deleted' field, please set 'SOFT_DELETE'=True in settings.
|
||||
Otherwise NotificationQuerySet.unread and NotificationQuerySet.read do NOT filter by 'deleted' field.
|
||||
"""
|
||||
raise ImproperlyConfigured(msg)
|
||||
|
||||
|
||||
class NotificationQuerySet(models.query.QuerySet):
|
||||
''' Notification QuerySet '''
|
||||
def unsent(self):
|
||||
return self.filter(emailed=False)
|
||||
|
||||
def sent(self):
|
||||
return self.filter(emailed=True)
|
||||
|
||||
def unread(self, include_deleted=False):
|
||||
"""Return only unread items in the current queryset"""
|
||||
if is_soft_delete() and not include_deleted:
|
||||
return self.filter(unread=True, deleted=False)
|
||||
|
||||
# When SOFT_DELETE=False, developers are supposed NOT to touch 'deleted' field.
|
||||
# In this case, to improve query performance, don't filter by 'deleted' field
|
||||
return self.filter(unread=True)
|
||||
|
||||
def read(self, include_deleted=False):
|
||||
"""Return only read items in the current queryset"""
|
||||
if is_soft_delete() and not include_deleted:
|
||||
return self.filter(unread=False, deleted=False)
|
||||
|
||||
# When SOFT_DELETE=False, developers are supposed NOT to touch 'deleted' field.
|
||||
# In this case, to improve query performance, don't filter by 'deleted' field
|
||||
return self.filter(unread=False)
|
||||
|
||||
def mark_all_as_read(self, recipient=None):
|
||||
"""Mark as read any unread messages in the current queryset.
|
||||
|
||||
Optionally, filter these by recipient first.
|
||||
"""
|
||||
# We want to filter out read ones, as later we will store
|
||||
# the time they were marked as read.
|
||||
qset = self.unread(True)
|
||||
if recipient:
|
||||
qset = qset.filter(recipient=recipient)
|
||||
|
||||
return qset.update(unread=False)
|
||||
|
||||
def mark_all_as_unread(self, recipient=None):
|
||||
"""Mark as unread any read messages in the current queryset.
|
||||
|
||||
Optionally, filter these by recipient first.
|
||||
"""
|
||||
qset = self.read(True)
|
||||
|
||||
if recipient:
|
||||
qset = qset.filter(recipient=recipient)
|
||||
|
||||
return qset.update(unread=True)
|
||||
|
||||
def deleted(self):
|
||||
"""Return only deleted items in the current queryset"""
|
||||
assert_soft_delete()
|
||||
return self.filter(deleted=True)
|
||||
|
||||
def active(self):
|
||||
"""Return only active(un-deleted) items in the current queryset"""
|
||||
assert_soft_delete()
|
||||
return self.filter(deleted=False)
|
||||
|
||||
def mark_all_as_deleted(self, recipient=None):
|
||||
"""Mark current queryset as deleted.
|
||||
Optionally, filter by recipient first.
|
||||
"""
|
||||
assert_soft_delete()
|
||||
qset = self.active()
|
||||
if recipient:
|
||||
qset = qset.filter(recipient=recipient)
|
||||
|
||||
return qset.update(deleted=True)
|
||||
|
||||
def mark_all_as_active(self, recipient=None):
|
||||
"""Mark current queryset as active(un-deleted).
|
||||
Optionally, filter by recipient first.
|
||||
"""
|
||||
assert_soft_delete()
|
||||
qset = self.deleted()
|
||||
if recipient:
|
||||
qset = qset.filter(recipient=recipient)
|
||||
|
||||
return qset.update(deleted=False)
|
||||
|
||||
def mark_as_unsent(self, recipient=None):
|
||||
qset = self.sent()
|
||||
if recipient:
|
||||
qset = qset.filter(recipient=recipient)
|
||||
return qset.update(emailed=False)
|
||||
|
||||
def mark_as_sent(self, recipient=None):
|
||||
qset = self.unsent()
|
||||
if recipient:
|
||||
qset = qset.filter(recipient=recipient)
|
||||
return qset.update(emailed=True)
|
||||
|
||||
|
||||
class Notification(models.Model):
|
||||
"""
|
||||
Action model describing the actor acting out a verb (on an optional
|
||||
target).
|
||||
Nomenclature based on http://activitystrea.ms/specs/atom/1.0/
|
||||
|
||||
Generalized Format::
|
||||
|
||||
<actor> <verb> <time>
|
||||
<actor> <verb> <target> <time>
|
||||
<actor> <verb> <action_object> <target> <time>
|
||||
|
||||
Examples::
|
||||
|
||||
<justquick> <reached level 60> <1 minute ago>
|
||||
<brosner> <commented on> <pinax/pinax> <2 hours ago>
|
||||
<washingtontimes> <started follow> <justquick> <8 minutes ago>
|
||||
<mitsuhiko> <closed> <issue 70> on <mitsuhiko/flask> <about 2 hours ago>
|
||||
|
||||
Unicode Representation::
|
||||
|
||||
justquick reached level 60 1 minute ago
|
||||
mitsuhiko closed issue 70 on mitsuhiko/flask 3 hours ago
|
||||
|
||||
HTML Representation::
|
||||
|
||||
<a href="http://oebfare.com/">brosner</a> commented on <a href="http://github.com/pinax/pinax">pinax/pinax</a> 2 hours ago # noqa
|
||||
|
||||
"""
|
||||
LEVELS = Choices('success', 'info', 'warning', 'error')
|
||||
level = models.CharField(choices=LEVELS, default=LEVELS.info, max_length=20)
|
||||
|
||||
recipient = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
blank=False,
|
||||
related_name='notifications',
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
unread = models.BooleanField(default=True, blank=False, db_index=True)
|
||||
|
||||
actor_content_type = models.ForeignKey(ContentType, related_name='notify_actor', on_delete=models.CASCADE)
|
||||
actor_object_id = models.CharField(max_length=255)
|
||||
actor = GenericForeignKey('actor_content_type', 'actor_object_id')
|
||||
|
||||
verb = models.CharField(max_length=255)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
|
||||
target_content_type = models.ForeignKey(
|
||||
ContentType,
|
||||
related_name='notify_target',
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
target_object_id = models.CharField(max_length=255, blank=True, null=True)
|
||||
target = GenericForeignKey('target_content_type', 'target_object_id')
|
||||
|
||||
action_object_content_type = models.ForeignKey(ContentType, blank=True, null=True,
|
||||
related_name='notify_action_object', on_delete=models.CASCADE)
|
||||
action_object_object_id = models.CharField(max_length=255, blank=True, null=True)
|
||||
action_object = GenericForeignKey('action_object_content_type', 'action_object_object_id')
|
||||
|
||||
timestamp = models.DateTimeField(default=timezone.now, db_index=True)
|
||||
|
||||
public = models.BooleanField(default=True, db_index=True)
|
||||
deleted = models.BooleanField(default=False, db_index=True)
|
||||
emailed = models.BooleanField(default=False, db_index=True)
|
||||
|
||||
data = JSONField(blank=True, null=True)
|
||||
objects = NotificationQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
ordering = ('-timestamp',)
|
||||
app_label = 'notifications'
|
||||
|
||||
def __unicode__(self):
|
||||
ctx = {
|
||||
'actor': self.actor,
|
||||
'verb': self.verb,
|
||||
'action_object': self.action_object,
|
||||
'target': self.target,
|
||||
'timesince': self.timesince()
|
||||
}
|
||||
if self.target:
|
||||
if self.action_object:
|
||||
return u'%(actor)s %(verb)s %(action_object)s on %(target)s %(timesince)s ago' % ctx
|
||||
return u'%(actor)s %(verb)s %(target)s %(timesince)s ago' % ctx
|
||||
if self.action_object:
|
||||
return u'%(actor)s %(verb)s %(action_object)s %(timesince)s ago' % ctx
|
||||
return u'%(actor)s %(verb)s %(timesince)s ago' % ctx
|
||||
|
||||
def __str__(self): # Adds support for Python 3
|
||||
return self.__unicode__()
|
||||
|
||||
def timesince(self, now=None):
|
||||
"""
|
||||
Shortcut for the ``django.utils.timesince.timesince`` function of the
|
||||
current timestamp.
|
||||
"""
|
||||
from django.utils.timesince import timesince as timesince_
|
||||
return timesince_(self.timestamp, now)
|
||||
|
||||
@property
|
||||
def slug(self):
|
||||
return id2slug(self.id)
|
||||
|
||||
def mark_as_read(self):
|
||||
if self.unread:
|
||||
self.unread = False
|
||||
self.save()
|
||||
|
||||
def mark_as_unread(self):
|
||||
if not self.unread:
|
||||
self.unread = True
|
||||
self.save()
|
||||
|
||||
|
||||
def notify_handler(verb, **kwargs):
|
||||
"""
|
||||
Handler function to create Notification instance upon action signal call.
|
||||
"""
|
||||
|
||||
# Pull the options out of kwargs
|
||||
kwargs.pop('signal', None)
|
||||
recipient = kwargs.pop('recipient')
|
||||
actor = kwargs.pop('sender')
|
||||
optional_objs = [
|
||||
(kwargs.pop(opt, None), opt)
|
||||
for opt in ('target', 'action_object')
|
||||
]
|
||||
public = bool(kwargs.pop('public', True))
|
||||
description = kwargs.pop('description', None)
|
||||
timestamp = kwargs.pop('timestamp', timezone.now())
|
||||
level = kwargs.pop('level', Notification.LEVELS.info)
|
||||
|
||||
# Check if User or Group
|
||||
if isinstance(recipient, Group):
|
||||
recipients = recipient.user_set.all()
|
||||
elif isinstance(recipient, (QuerySet, list)):
|
||||
recipients = recipient
|
||||
else:
|
||||
recipients = [recipient]
|
||||
|
||||
new_notifications = []
|
||||
|
||||
for recipient in recipients:
|
||||
newnotify = Notification(
|
||||
recipient=recipient,
|
||||
actor_content_type=ContentType.objects.get_for_model(actor),
|
||||
actor_object_id=actor.pk,
|
||||
verb=text_type(verb),
|
||||
public=public,
|
||||
description=description,
|
||||
timestamp=timestamp,
|
||||
level=level,
|
||||
)
|
||||
|
||||
# Set optional objects
|
||||
for obj, opt in optional_objs:
|
||||
if obj is not None:
|
||||
setattr(newnotify, '%s_object_id' % opt, obj.pk)
|
||||
setattr(newnotify, '%s_content_type' % opt,
|
||||
ContentType.objects.get_for_model(obj))
|
||||
|
||||
if kwargs and EXTRA_DATA:
|
||||
newnotify.data = kwargs
|
||||
|
||||
newnotify.save()
|
||||
new_notifications.append(newnotify)
|
||||
|
||||
return new_notifications
|
||||
|
||||
|
||||
# connect the signal
|
||||
notify.connect(notify_handler, dispatch_uid='notifications.models.notification')
|
||||
class Meta(AbstractNotification.Meta):
|
||||
abstract = False
|
||||
swappable = swappable_setting('notifications', 'Notification')
|
||||
|
|
|
|||
3
setup.py
3
setup.py
|
|
@ -29,7 +29,8 @@ setup(
|
|||
'django>=1.7',
|
||||
'django-model-utils>=2.0.3',
|
||||
'jsonfield>=1.0.3',
|
||||
'pytz'
|
||||
'pytz',
|
||||
'swapper'
|
||||
],
|
||||
test_requires=[
|
||||
'django>=1.7',
|
||||
|
|
|
|||
Loading…
Reference in a new issue