django-notifications is a GitHub notification alike app for Django, it was derived from django-activity-stream
The major difference between django-notifications
and django-activity-stream
:
django-notifications
is for building something like Github "Notifications"- While
django-activity-stream
is for building Github "News Feed"
Notifications are actually actions events, which are categorized by four main components.
Actor
. The object that performed the activity.Verb
. The verb phrase that identifies the action of the activity.Action Object
. (Optional) The object linked to the action itself.Target
. (Optional) The object to which the activity was performed.
Actor
, Action Object
and Target
are GenericForeignKeys
to any
arbitrary Django object. An action is a description of an action that
was performed (Verb
) at some instant in time by some Actor
on some
optional Target
that results in an Action Object
getting
created/updated/deleted.
For example: justquick (actor)
closed (verb)
issue
2
(action_object)
on
activity-stream
(target)
12 hours ago
Nomenclature of this specification is based on the Activity Streams Spec: http://activitystrea.ms/specs/atom/1.0/
- Python 3.7, 3.8, 3.9, 3.10, 3.11
- Django 3.2, 4.0, 4.1
Installation is easy using pip
and will install all required
libraries.
$ pip install django-notifications-hq
or get it from source
$ git clone https://github.com/django-notifications/django-notifications
$ cd django-notifications
$ python setup.py sdist
$ pip install dist/django-notifications-hq*
Note that django-model-utils will be installed: this is required for the pass-through QuerySet manager.
Then to add the Django Notifications to your project add the app
notifications
to your INSTALLED_APPS
and urlconf.
The app should go somewhere after all the apps that are going to be
generating notifications like django.contrib.auth
INSTALLED_APPS = (
'django.contrib.auth',
...
'notifications',
...
)
Add the notifications urls to your urlconf:
urlpatterns = [
...
path('inbox/notifications/', include('notifications.urls', namespace='notifications')),
...
]
To run schema migration, execute
python manage.py migrate notifications
.
Generating notifications is probably best done in a separate signal.
from django.db.models.signals import post_save
from notifications.signals import notify
from myapp.models import MyModel
def my_handler(sender, instance, created, **kwargs):
notify.send(instance, verb='was saved')
post_save.connect(my_handler, sender=MyModel)
To generate an notification anywhere in your code, simply import the notify signal and send it with your actor, recipient, and verb.
from notifications.signals import notify
notify.send(user, recipient=user, verb='you reached level 10')
The complete syntax is.
notify.send(actor, recipient, verb, action_object, target, level, description, public, timestamp, **kwargs)
Arguments:
- actor: An object of any type. (Required) Note: Use sender instead of actor if you intend to use keyword arguments
- recipient: A Group or a User QuerySet or a list of User. (Required)
- verb: An string. (Required)
- action_object: An object of any type. (Optional)
- target: An object of any type. (Optional)
- level: One of Notification.LEVELS ('success', 'info', 'warning', 'error') (default=info). (Optional)
- description: An string. (Optional)
- public: An boolean (default=True). (Optional)
- timestamp: An tzinfo (default=timezone.now()). (Optional)
You can attach arbitrary data to your notifications by doing the following:
- Add to your settings.py:
DJANGO_NOTIFICATIONS_CONFIG = { 'USE_JSONFIELD': True}
Then, any extra arguments you pass to notify.send(...)
will be
attached to the .data
attribute of the notification object. These will
be serialised using the JSONField's serialiser, so you may need to take
that into account: using only objects that will be serialised is a good
idea.
By default, delete/(?P<slug>\d+)/
deletes specified notification
record from DB. You can change this behaviour to "mark
Notification.deleted
field as True
" by:
- Add to your settings.py:
DJANGO_NOTIFICATIONS_CONFIG = { 'SOFT_DELETE': True}
With this option, QuerySet methods unread
and read
contain one more
filter: deleted=False
. Meanwhile, QuerySet methods deleted
,
active
, mark_all_as_deleted
, mark_all_as_active
are turned on. See
more details in QuerySet methods section.
Using django-model-utils
, we get the ability to add queryset methods
to not only the manager, but to all querysets that will be used,
including related objects. This enables us to do things like:
Notification.objects.unread()
which returns all unread notifications. To do this for a single user, we can do:
user = User.objects.get(pk=pk)
user.notifications.unread()
There are some other QuerySet methods, too.
Return all of the unsent notifications, filtering the current queryset. (emailed=False)
Return all of the sent notifications, filtering the current queryset. (emailed=True)
Return all of the unread notifications, filtering the current queryset.
When SOFT_DELETE=True
, this filter contains deleted=False
.
Return all of the read notifications, filtering the current queryset.
When SOFT_DELETE=True
, this filter contains deleted=False
.
Mark all of the unread notifications in the queryset (optionally also
filtered by recipient
) as read.
Mark all of the read notifications in the queryset (optionally also
filtered by recipient
) as unread.
Mark all of the unsent notifications in the queryset (optionally also
filtered by recipient
) as sent.
Mark all of the sent notifications in the queryset (optionally also
filtered by recipient
) as unsent.
Return all notifications that have deleted=True
, filtering the current
queryset. Must be used with SOFT_DELETE=True
.
Return all notifications that have deleted=False
, filtering the
current queryset. Must be used with DELETE=True
.
Mark all notifications in the queryset (optionally also filtered by
recipient
) as deleted=True
. Must be used with DELETE=True
.
Mark all notifications in the queryset (optionally also filtered by
recipient
) as deleted=False
. Must be used with SOFT_DELETE=True
.
A wrapper for Django's timesince
function.
Mark the current object as read.
Put {% load notifications_tags %}
in the template before
you actually use notification tags.
{% notifications_unread %}
Give the number of unread notifications for a user, or nothing (an empty string) for an anonymous user.
Storing the count in a variable for further processing is advised, such as:
{% notifications_unread as unread_count %}
...
{% if unread_count %}
You have <strong>{{ unread_count }}</strong> unread notifications.
{% endif %}
To ensure users always have the most up-to-date notifications,
django-notifications
includes a simple javascript API for
updating specific fields within a django template.
There are two possible API calls that can be made:
-
api/unread_count/
that returns a javascript object with 1 key:unread_count
eg:{"unread_count":1}
-
api/unread_list/
that returns a javascript object with 2 keys:unread_count
andunread_list
eg:{ "unread_count":1, "unread_list":[--list of json representations of notifications--] }
Representations of notifications are based on the django method:
model_to_dict
Query string arguments:
- max - maximum length of unread list.
- mark_as_read - mark notification in list as read.
For example, get
api/unread_list/?max=3&mark_as_read=true
returns 3 notifications and mark them read (remove from list on next request).The list outputs
target_url
,actor_url
,action_object_url
. This URL is generated from standard DjangoModel.get_absolute_url()
or you can override the URL just for notifications by implementingModel.get_url_for_notifications(notification, request)
.
-
Put
{% load notifications_tags %}
in the template before you actually use notification tags. -
In the area where you are loading javascript resources add the following tags in the order below:
<script src="{% static 'notifications/notify.js' %}" type="text/javascript"></script> {% register_notify_callbacks callbacks='fill_notification_list,fill_notification_badge' %}
register_notify_callbacks
takes the following arguments:badge_class
(defaultlive_notify_badge
) - The identifierclass
of the element to show the unread count, that will be periodically updated.menu_class
(defaultlive_notify_list
) - The identifierclass
of the element to insert a list of unread items, that will be periodically updated.refresh_period
(default15
) - How often to fetch unread items from the server (integer in seconds).fetch
(default5
) - How many notifications to fetch each time.callbacks
(default<empty string>
) - A comma-separated list of javascript functions to call each period.api_name
(defaultlist
) - The name of the API to call (this can be eitherlist
orcount
).mark_as_read
(defaultFalse
) - Marks notifications as read when fetched.
-
To insert a live-updating unread count, use the following template:
{% live_notify_badge %}
live_notify_badge
takes the following arguments:badge_class
(defaultlive_notify_badge
) - The identifierclass
for the<span>
element that will be created to show the unread count.
-
To insert a live-updating unread list, use the following template:
{% live_notify_list %}
live_notify_list
takes the following arguments:list_class
(defaultlive_notify_list
) - The identifierclass
for the<ul>
element that will be created to insert the list of notifications into.
The Live-updater can be incorporated into bootstrap with minimal code.
To create a live-updating bootstrap badge containing the unread count, simply use the template tag:
{% live_notify_badge badge_class="badge" %}
To create a live-updating bootstrap dropdown menu containing a selection of recent unread notifications, simply use the template tag:
{% live_notify_list list_class="dropdown-menu" %}
While the live notifier for unread counts should suit most use cases, users may wish to alter how unread notifications are shown.
The callbacks
argument of the register_notify_callbacks
dictates
which javascript functions are called when the unread api call is made.
To add a custom javascript callback, simply add this to the list, like so:
{% register_notify_callbacks callbacks='fill_notification_badge,my_special_notification_callback' %}
The above would cause the callback to update the unread count badge, and
would call the custom function
my_special_notification_callback
. All callback
functions are passed a single argument by convention called
data
, which contains the entire result from the API.
For example, the below function would get the recent list of unread messages and log them to the console:
function my_special_notification_callback(data) {
for (var i=0; i < data.unread_list.length; i++) {
msg = data.unread_list[i];
console.log(msg);
}
}
- Clone the repo
- Run
./manage.py runserver
- Browse to
yourserverip/test/
- Click 'Make a notification' and a new notification should appear in the list in 5-10 seconds.
See here - http://www.django-rest-framework.org/api-guide/relations/#generic-relationships
In this example the target object can be of type Foo or Bar and the appropriate serializer will be used.
class GenericNotificationRelatedField(serializers.RelatedField):
def to_representation(self, value):
if isinstance(value, Foo):
serializer = FooSerializer(value)
if isinstance(value, Bar):
serializer = BarSerializer(value)
return serializer.data
class NotificationSerializer(serializers.Serializer):
recipient = PublicUserSerializer(User, read_only=True)
unread = serializers.BooleanField(read_only=True)
target = GenericNotificationRelatedField(read_only=True)
Thanks to @DaWy
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:
#In your_app/models.py
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
You will require to define NOTIFICATIONS_NOTIFICATION_MODEL
setting in
setting.py
as follows:
# In your_project/settings.py
NOTIFICATIONS_NOTIFICATION_MODEL = 'your_app.Notification'
Sending email to users has not been integrated into this library. So for
now you need to implement it if needed. There is a reserved field
Notification.emailed
to make it easier.
A sample app has been implemented in
notifications/tests/sample_notifications
that extends
django-notifications
with the sole purpose of testing its
extensibility. You can run the SAMPLE APP by setting the environment
variable SAMPLE_APP
as follows
export SAMPLE_APP=1
# Run the Django development server with sample_notifications app installed
python manage.py runserver
# Unset SAMPLE_APP to remove sample_notifications app from list of INSTALLED_APPS
unset SAMPLE_APP
Core contributors (in alphabetical order):
We are looking for contributors, for anyone who'd like to contribute and willing to put time and energy on this project, please contact Yang Yubo.