Skip to main content

GitHub notifications alike app for Django (community fork).

Project description

django-notifications (community fork)

tests PyPI Python versions Django versions

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-notifications team (see AUTHORS.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

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-notifications is for building something like GitHub "Notifications".
  • django-activity-stream is 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() uses bulk_create internally, so post_save handlers registered on the Notification model 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 sender instead of actor if you're passing keyword arguments.
  • recipient (required) — a single User, a Group, a User queryset, or a list of Users.
  • 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 to timezone.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's timesince.
  • obj.naturalday() / obj.naturaltime() — wrappers around the django.contrib.humanize helpers of the same name.
  • obj.mark_as_read() / obj.mark_as_unread() — flip unread on a single row.
  • obj.slug — URL-safe encoded id, used by the mark-as-read, mark-as-unread, and delete views.

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:

  1. api/unread_count/{"unread_count": 1}
  2. 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_read query parameter above is accessible via GET, and it's gated to the requesting user's own unread rows.

Wiring it up

  1. Load {% load notifications_tags %} in the template.

  2. 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_callbacks renders 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_callbacks arguments:

    • badge_class (default live_notify_badge) — CSS class of the unread-count element.
    • menu_class (default live_notify_list) — CSS class of the list element.
    • refresh_period (default 15) — poll interval in seconds.
    • fetch (default 5) — how many notifications to fetch each poll.
    • callbacks (default '') — comma-separated list of JS functions to call on each poll.
    • api_name (default list) — either list or count.
    • mark_as_read (default False) — mark fetched notifications as read.
    • nonce (default None) — if set, emitted as the nonce attribute on the <script> tag, for strict CSP setups that allow JSON blocks by nonce.
  3. Insert a live-updating badge:

    {% live_notify_badge %}
    

    Takes badge_class (default live_notify_badge) — CSS class for the generated <span>.

  4. Insert a live-updating list:

    {% live_notify_list %}
    

    Takes list_class (default live_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

  1. Clone the repo.
  2. Run python manage.py runserver.
  3. Browse to http://127.0.0.1:8000/test/.
  4. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_notifications_community-1.11.3.post1.tar.gz (46.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

File details

Details for the file django_notifications_community-1.11.3.post1.tar.gz.

File metadata

File hashes

Hashes for django_notifications_community-1.11.3.post1.tar.gz
Algorithm Hash digest
SHA256 e6112764aab9f0a490d4fd093e8d9f6a2d50c673b3a28108257ebe4ae0affb84
MD5 96ed3c6751808a0296ad52d3637dee13
BLAKE2b-256 a06646418bd582aeb5b02bc8fe348f174b864457fddd2ee2fe1d1aefd798e775

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_notifications_community-1.11.3.post1.tar.gz:

Publisher: release.yml on django-notifications-community/django-notifications-community

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_notifications_community-1.11.3.post1-py3-none-any.whl.

File metadata

File hashes

Hashes for django_notifications_community-1.11.3.post1-py3-none-any.whl
Algorithm Hash digest
SHA256 6d1e56805a0068c447198bc94c8481522ad03f9c555d23c94172cd796d5cdfcf
MD5 6cce53fd3692a252f7625dd5c0bcdb64
BLAKE2b-256 8e228f28d81c320630f387586354b2662875054c0b557ccbe710232bf331471b

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_notifications_community-1.11.3.post1-py3-none-any.whl:

Publisher: release.yml on django-notifications-community/django-notifications-community

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page