GitHub notifications alike app for Django (community fork).
Project description
django-notifications (community fork)
This is a community-maintained fork of django-notifications/django-notifications.
The original project appears unmaintained: version 1.9.0 (with Django 5.x and Python 3.13 support) has been sitting unreleased on
master, and requests for PyPI and maintainer access have gone unanswered. See the discussion in upstream issue #416 for context.All credit for the original work goes to Justin Quick, Yang Yubo, and the
django-notificationsteam (seeAUTHORS.txt). This fork exists solely to continue shipping releases to PyPI and keep the project compatible with current Django and Python versions. If upstream resumes active maintenance, we will happily coordinate and, where appropriate, redirect users back.Drop-in replacement. The Python import path is unchanged (
import notifications). To switch from the original package:pip uninstall django-notifications-hq pip install django-notifications-community
Contents
- Overview
- Requirements
- Installation
- Settings
- Generating notifications
- QuerySet and model methods
- Template tags
- Live-updater API
- Serializing the notification model
AbstractNotificationmodel- Notes
- Credits
- Contributing
Overview
django-notifications is a GitHub-style notifications app for Django, derived from django-activity-stream.
The major difference between django-notifications and
django-activity-stream:
django-notificationsis for building something like GitHub "Notifications".django-activity-streamis for building a GitHub "News Feed".
A notification is an action event, categorized by four components:
Actor— the object that performed the activity.Verb— the verb phrase that identifies the action.Action Object— (optional) the object linked to the action itself.Target— (optional) the object the activity was performed on.
Actor, Action Object, and Target are GenericForeignKeys to any
arbitrary Django object. An action describes something that was performed
(Verb) at some instant in time by an Actor on an optional Target,
resulting in an Action Object being created, updated, or deleted.
For example: justquick (actor) closed
(verb) issue 2
(action_object) on
activity-stream
(target) 12 hours ago.
Nomenclature is based on the Activity Streams spec: https://activitystrea.ms/specs/atom/1.0/.
Requirements
- Python 3.10, 3.11, 3.12, 3.13
- Django 4.2, 5.1, 5.2
Installation
Install with pip:
pip install django-notifications-community
or with uv:
uv add django-notifications-community
Add notifications to INSTALLED_APPS. It should come after any apps that
generate notifications (like django.contrib.auth):
INSTALLED_APPS = [
'django.contrib.auth',
...
'notifications',
...
]
Include the notifications URLs in your urlconf:
urlpatterns = [
...
path('inbox/notifications/', include('notifications.urls', namespace='notifications')),
...
]
Run the migrations:
python manage.py migrate notifications
Settings
All configuration lives in a single DJANGO_NOTIFICATIONS_CONFIG dict in
settings.py. Defaults:
DJANGO_NOTIFICATIONS_CONFIG = {
'PAGINATE_BY': 20,
'USE_JSONFIELD': False,
'SOFT_DELETE': False,
'NUM_TO_FETCH': 10,
'CACHE_TIMEOUT': 2,
}
| Key | Default | Purpose |
|---|---|---|
PAGINATE_BY |
20 |
Page size for the list views. |
USE_JSONFIELD |
False |
Persist extra kwargs passed to notify.send() on Notification.data. |
SOFT_DELETE |
False |
Flip the delete view from row removal to setting deleted=True. See Soft delete. |
NUM_TO_FETCH |
10 |
Default page size for the live-updater JSON endpoints. |
CACHE_TIMEOUT |
2 |
Seconds to cache user.notifications.unread().count. 0 disables caching. |
Extra data
With USE_JSONFIELD on, any extra keyword arguments passed to
notify.send(...) are stored on the notification's .data attribute, JSON
serialized. Pass only JSON-serializable values.
Soft delete
With SOFT_DELETE on, delete/<int:slug>/ flips Notification.deleted to
True instead of removing the row. The unread and read querysets gain a
deleted=False filter, and the deleted, active, mark_all_as_deleted,
and mark_all_as_active queryset methods become usable. See
QuerySet methods below.
Generating notifications
The typical pattern is to send a notification from a signal handler on one of your own models:
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)
You can also call notify.send() directly anywhere in your code:
from notifications.signals import notify
notify.send(user, recipient=user, verb='you reached level 10')
Note:
notify.send()usesbulk_createinternally, sopost_savehandlers registered on theNotificationmodel itself will not fire. See Bulk creation and signals.
Full signature:
notify.send(
actor, recipient, verb,
action_object, target,
level, description, public, timestamp,
**kwargs,
)
Arguments:
- actor (required) — any object. Use
senderinstead ofactorif you're passing keyword arguments. - recipient (required) — a single
User, aGroup, aUserqueryset, or a list ofUsers. - verb (required) — a string.
- action_object — any object.
- target — any object.
- level — one of
Notification.LEVELS('success','info','warning','error'). Defaults to'info'. - description — a string.
- public — a bool. Defaults to
True. - timestamp — a
datetime. Defaults totimezone.now().
QuerySet and model methods
QuerySet methods
The Notification manager is built from a custom QuerySet via Django's
QuerySet.as_manager(), so queryset methods are available on the manager,
on related managers, and on any further-filtered queryset:
Notification.objects.unread()
user = User.objects.get(pk=pk)
user.notifications.unread()
Available methods:
| Method | Purpose |
|---|---|
qs.unread() |
Unread notifications. With SOFT_DELETE=True, excludes deleted. |
qs.read() |
Read notifications. With SOFT_DELETE=True, excludes deleted. |
qs.unsent() |
emailed=False. |
qs.sent() |
emailed=True. |
qs.mark_all_as_read([recipient]) |
Mark all unread rows as read. |
qs.mark_all_as_unread([recipient]) |
Mark all read rows as unread. |
qs.mark_as_sent([recipient]) |
Mark unsent rows as sent. |
qs.mark_as_unsent([recipient]) |
Mark sent rows as unsent. |
qs.deleted() |
Rows with deleted=True. Requires SOFT_DELETE=True. |
qs.active() |
Rows with deleted=False. Requires SOFT_DELETE=True. |
qs.mark_all_as_deleted([recipient]) |
Flip to deleted=True. Requires SOFT_DELETE=True. |
qs.mark_all_as_active([recipient]) |
Flip to deleted=False. Requires SOFT_DELETE=True. |
Model methods
obj.timesince([datetime])— wrapper around Django'stimesince.obj.naturalday()/obj.naturaltime()— wrappers around thedjango.contrib.humanizehelpers of the same name.obj.mark_as_read()/obj.mark_as_unread()— flipunreadon a single row.obj.slug— URL-safe encoded id, used by themark-as-read,mark-as-unread, anddeleteviews.
Template tags
Load the tag library in your template:
{% load notifications_tags %}
notifications_unread
{% notifications_unread %}
Returns the unread count for the current user, or an empty string for anonymous users. Storing it in a variable is usually what you want:
{% notifications_unread as unread_count %}
{% if unread_count %}
You have <strong>{{ unread_count }}</strong> unread notifications.
{% endif %}
Live-updater API
A small JavaScript API periodically polls the server to keep unread counts and lists up to date. Two endpoints are provided for unread data:
api/unread_count/—{"unread_count": 1}api/unread_list/—{"unread_count": 1, "unread_list": [ ... ]}
Matching api/all_count/ and api/all_list/ endpoints cover all
notifications (read and unread) and follow the same key pattern —
{scope}_count and {scope}_list, where scope mirrors the endpoint
segment (so all_count, all_list).
Notification JSON is produced via Django's model_to_dict. Each list entry
also exposes target_url, actor_url, and action_object_url, which come
from Model.get_absolute_url() by default. You can override the URL
specifically for notifications by implementing
Model.get_url_for_notifications(notification, request) on the related
model.
Query string arguments (list endpoints):
- max — maximum length of the returned list.
- mark_as_read — if truthy, mark the returned notifications as read.
Example: GET api/unread_list/?max=3&mark_as_read=true returns three
notifications and marks them read, so they'll drop off the next request.
Security note: the state-changing views (
mark_as_read,mark_as_unread,mark_all_as_read,delete) require POST and a CSRF token. Only the list-with-mark_as_readquery parameter above is accessible via GET, and it's gated to the requesting user's own unread rows.
Wiring it up
-
Load
{% load notifications_tags %}in the template. -
Include the JS and register the callbacks:
<script src="{% static 'notifications/notify.js' %}"></script> {% register_notify_callbacks callbacks='fill_notification_list,fill_notification_badge' %}
Since 1.11.2,
register_notify_callbacksrenders its configuration as a<script type="application/json">block rather than inline JS, so it works under strict Content Security Policies without a'unsafe-inline'allowance.register_notify_callbacksarguments:badge_class(defaultlive_notify_badge) — CSS class of the unread-count element.menu_class(defaultlive_notify_list) — CSS class of the list element.refresh_period(default15) — poll interval in seconds.fetch(default5) — how many notifications to fetch each poll.callbacks(default'') — comma-separated list of JS functions to call on each poll.api_name(defaultlist) — eitherlistorcount.mark_as_read(defaultFalse) — mark fetched notifications as read.nonce(defaultNone) — if set, emitted as thenonceattribute on the<script>tag, for strict CSP setups that allow JSON blocks by nonce.
-
Insert a live-updating badge:
{% live_notify_badge %}
Takes
badge_class(defaultlive_notify_badge) — CSS class for the generated<span>. -
Insert a live-updating list:
{% live_notify_list %}
Takes
list_class(defaultlive_notify_list) — CSS class for the generated<ul>.
Using the live-updater with Bootstrap
Reuse the template tags with Bootstrap's classes:
{% live_notify_badge badge_class="badge" %}
{% live_notify_list list_class="dropdown-menu" %}
Custom JavaScript callbacks
The callbacks argument of register_notify_callbacks is a comma-separated
list of JS function names called on every poll. Each function receives one
argument, data, containing the entire API response.
{% register_notify_callbacks callbacks='fill_notification_badge,my_special_notification_callback' %}
function my_special_notification_callback(data) {
for (const msg of data.unread_list) {
console.log(msg);
}
}
Testing the live-updater
- Clone the repo.
- Run
python manage.py runserver. - Browse to
http://127.0.0.1:8000/test/. - Click "Make a notification" — a new notification should appear in the list within 5-10 seconds.
Serializing the notification model
See the DRF guide on generic relationships. The example below picks a serializer based on the target type:
class GenericNotificationRelatedField(serializers.RelatedField):
def to_representation(self, value):
if isinstance(value, Foo):
return FooSerializer(value).data
if isinstance(value, Bar):
return BarSerializer(value).data
class NotificationSerializer(serializers.Serializer):
recipient = PublicUserSerializer(read_only=True)
unread = serializers.BooleanField(read_only=True)
target = GenericNotificationRelatedField(read_only=True)
Thanks to @DaWy.
AbstractNotification model
If you need to extend the notification model with extra fields, subclass
AbstractNotification:
# your_app/models.py
from django.db import models
from notifications.base.models import AbstractNotification
class Notification(AbstractNotification):
category = models.ForeignKey('myapp.Category', on_delete=models.CASCADE)
class Meta(AbstractNotification.Meta):
abstract = False
Then point the library at your model in settings.py:
NOTIFICATIONS_NOTIFICATION_MODEL = 'your_app.Notification'
As of 1.11.3, swapping is resolved via Django's built-in app loading rather
than the third-party swapper package. No configuration changes are
required when upgrading.
Notes
Email notifications
Email delivery is not built in. The Notification.emailed field is
reserved to make it easier to track whether you've sent one.
Bulk creation and signals
As of 1.11.0, notify.send() uses bulk_create, which means Django's
post_save signal does not fire for the Notification rows it writes.
If you previously relied on post_save to trigger side effects (email,
push, etc.), switch to connecting the notify signal instead:
from notifications.signals import notify
def handle_notifications(sender, verb, **kwargs):
recipient = kwargs.get('recipient')
# side effect here (send email, push, ...)
notify.connect(handle_notifications)
The notify signal fires once per notify.send() call, before the rows
are written. If you need access to the saved Notification objects, use
the return value of notify.send() instead:
from notifications.signals import notify
responses = notify.send(
sender=user,
recipient=target_user,
verb='commented on',
target=post,
)
# Each response is a (handler_function, return_value) tuple.
# The default handler returns the list of created Notification objects.
for handler, notifications in responses:
for notification in notifications:
send_push(notification.recipient, str(notification))
Sample app
A sample app lives at notifications/tests/sample_notifications and
exercises the AbstractNotification swap path. Run it by exporting
SAMPLE_APP=1:
export SAMPLE_APP=1
python manage.py runserver
unset SAMPLE_APP
Credits
Upstream contributors
The original django-notifications was built by (alphabetical):
See AUTHORS.txt for the full contributor list.
Fork maintainers
This community fork is maintained by django-notifications-community contributors. Please use this repo's issue tracker for fork-specific bugs and features rather than contacting the upstream authors.
Contributing
See CONTRIBUTING.md for development setup, testing,
and pull request guidelines.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_notifications_community-1.11.3.tar.gz.
File metadata
- Download URL: django_notifications_community-1.11.3.tar.gz
- Upload date:
- Size: 46.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c8cc46e4361bcff8a83a0f65bf75e2df407c42a7949d9b28cdd0bec685f21cda
|
|
| MD5 |
2650452c4f0f361ab2f0f08246aeff7c
|
|
| BLAKE2b-256 |
ecd47821df4c297db8c7440e736f5136dfdd8355cc8758d9ed10b34b26af1b8a
|
Provenance
The following attestation bundles were made for django_notifications_community-1.11.3.tar.gz:
Publisher:
release.yml on django-notifications-community/django-notifications-community
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_notifications_community-1.11.3.tar.gz -
Subject digest:
c8cc46e4361bcff8a83a0f65bf75e2df407c42a7949d9b28cdd0bec685f21cda - Sigstore transparency entry: 1306574013
- Sigstore integration time:
-
Permalink:
django-notifications-community/django-notifications-community@89d2a93fa3ea62e838b98cd959f3bfe07cf6e356 -
Branch / Tag:
refs/tags/v1.11.3 - Owner: https://github.com/django-notifications-community
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@89d2a93fa3ea62e838b98cd959f3bfe07cf6e356 -
Trigger Event:
push
-
Statement type:
File details
Details for the file django_notifications_community-1.11.3-py3-none-any.whl.
File metadata
- Download URL: django_notifications_community-1.11.3-py3-none-any.whl
- Upload date:
- Size: 51.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ab3e416eeb67378046ed8a07b392727a8cd3556fdbf263ba5a59124b7a7b817f
|
|
| MD5 |
e19ac4711dd7019909b05dac06659cb2
|
|
| BLAKE2b-256 |
a4f43c79a4676465d90733cd289cee583c6abf265d0c1037ab5839e261624330
|
Provenance
The following attestation bundles were made for django_notifications_community-1.11.3-py3-none-any.whl:
Publisher:
release.yml on django-notifications-community/django-notifications-community
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_notifications_community-1.11.3-py3-none-any.whl -
Subject digest:
ab3e416eeb67378046ed8a07b392727a8cd3556fdbf263ba5a59124b7a7b817f - Sigstore transparency entry: 1306574111
- Sigstore integration time:
-
Permalink:
django-notifications-community/django-notifications-community@89d2a93fa3ea62e838b98cd959f3bfe07cf6e356 -
Branch / Tag:
refs/tags/v1.11.3 - Owner: https://github.com/django-notifications-community
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@89d2a93fa3ea62e838b98cd959f3bfe07cf6e356 -
Trigger Event:
push
-
Statement type: